Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.regex.Pattern;
/**
* A peephole optimization that minimizes code by simplifying conditional
* expressions, replacing IFs with HOOKs, replacing object constructors
* with literals, and simplifying returns.
*
*/
public class PeepholeSubstituteAlternateSyntax
extends AbstractPeepholeOptimization {
private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND);
private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR);
static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS =
DiagnosticType.error(
"JSC_INVALID_REGULAR_EXPRESSION_FLAGS",
"Invalid flags to RegExp constructor: {0}");
static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE
= new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return input.getType() != Token.FUNCTION;
}
};
/**
* Tries apply our various peephole minimizations on the passed in node.
*/
@Override
@SuppressWarnings("fallthrough")
public Node optimizeSubtree(Node node) {
switch(node.getType()) {
case Token.RETURN:
return tryReduceReturn(node);
case Token.NOT:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeNot(node);
case Token.IF:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeIf(node);
case Token.EXPR_RESULT:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.HOOK:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.WHILE:
case Token.DO:
tryMinimizeCondition(NodeUtil.getConditionExpression(node));
return node;
case Token.FOR:
if (!NodeUtil.isForIn(node)) {
tryMinimizeCondition(
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>NodeUtil.getConditionExpression(node));
}
return node;
case Token.NEW:
node = tryFoldStandardConstructors(node);
if (node.getType() != Token.CALL) {
return node;
}
// Fall through on purpose because tryFoldStandardConstructors() may
// convert a NEW node into a CALL node
case Token.CALL:
return tryFoldLiteralConstructor(node);
default:
return node; //Nothing changed
}
}
/**
* Reduce "return undefined" or "return void 0" to simply "return".
*
* Returns the replacement for n, or the original if no change was made.
*/
private Node tryReduceReturn(Node n) {
Node result = n.getFirstChild();
boolean possibleException = result != null &&
ControlFlowAnalysis.mayThrowException(result);
// Try to use a substitute that with a break because it is shorter.
// First lets pretend it is a break with no labels.
Node breakTarget = n;
boolean safe = true;
for (;!ControlFlowAnalysis.isBreakTarget(breakTarget, null /* no label */);
breakTarget = breakTarget.getParent()) {
if (NodeUtil.isFunction(breakTarget) ||
breakTarget.getType() == Token.SCRIPT) {
// We can switch the return to a break if the return value has
// side effect and it must encounter a finally.
// example: return alert('a') -> finally { alert('b') } ->
// return alert('a')
// prints a then b. If the first return is a break,
// it prints b then a.
safe = false;
break;
}
}
Node follow = ControlFlowAnalysis.computeFollowNode(breakTarget);
// Skip pass all the finally blocks because both the break and return will
// also trigger all the finally blocks. However, the order of execution is
// slightly changed. Consider:
//
// return a() -> finally { b() } -> return a()
//
// which would call a() first. However, changing the first return to a
// break will result in calling b().
while (follow != null &&
NodeUtil.isTryFinallyNode(follow.getParent(), follow)) {
if (result != null &&
// TODO(user): Use the new side effects API for more accuracy.
(NodeUtil.canBeSideEffected(result) ||
NodeUtil.mayHaveSideEffects(result))) {
safe = false;
break;
}
follow = ControlFlowAnalysis.computeFollowNode(follow);
}
if (safe) {
if (follow == null) {
// When follow is null, this mean the follow of a break target is the
// end of a function. This means a break is same as return.
if (result == null) {
n.setType(Token.BREAK);
reportCodeChange();
return n;
}
} else if (follow.getType() == Token.RETURN &&
(result == follow.getFirstChild() ||
(result != null && follow.hasChildren() &&
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
result.checkTreeEqualsSilent(follow.getFirstChild())) &&
ControlFlowAnalysis.getExceptionHandler(n) ==
ControlFlowAnalysis.getExceptionHandler(follow)
)) {
// When the follow is a return, if both doesn't return anything
// or both returns the same thing. This mean we can replace it with a
// break.
n.removeChildren();
n.setType(Token.BREAK);
reportCodeChange();
return n;
}
// If any of the above is executed, we must return because n is no longer
// a "return" node.
}
// TODO(user): consider cases such as if (x) { return 1} return 1;
if (result != null) {
switch (result.getType()) {
case Token.VOID:
Node operand = result.getFirstChild();
if (!mayHaveSideEffects(operand)) {
n.removeFirstChild();
reportCodeChange();
}
break;
case Token.NAME:
String name = result.getString();
if (name.equals("undefined")) {
n.removeFirstChild();
reportCodeChange();
}
break;
default:
//Do nothing
break;
}
}
return n;
}
/**
* Try to minimize NOT nodes such as !(x==y).
*
* Returns the replacement for n or the original if no change was made
*/
private Node tryMinimizeNot(Node n) {
Node parent = n.getParent();
Node notChild = n.getFirstChild();
// negative operator of the current one : == -> != for instance.
int complementOperator;
switch (notChild.getType()) {
case Token.EQ:
complementOperator = Token.NE;
break;
case Token.NE:
complementOperator = Token.EQ;
break;
case Token.SHEQ:
complementOperator = Token.SHNE;
break;
case Token.SHNE:
complementOperator = Token.SHEQ;
break;
// GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN.
default:
return n;
}
Node newOperator = n.removeFirstChild();
newOperator.setType(complementOperator);
parent.replaceChild(n, newOperator);
reportCodeChange();
return newOperator;
}
/**
* Try turning IF nodes into smaller HOOKs
*
* Returns the replacement for n or the original if no replacement was
* necessary.
*/
private Node tryMinimizeIf(Node n) {
Node parent = n.getParent();
Node cond = n.getFirstChild();
/* If the condition is a literal, we'll let other
* optimizations try to remove useless code.
*/
if (NodeUtil.isLiteralValue(cond, true)) {
return n;
}
Node thenBranch = cond.getNext();
Node elseBranch = thenBranch.getNext();
if (elseBranch == null) {
if (isFoldableExpressBlock(thenBranch)) {
Node expr = getBlockExpression(thenBranch);
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>, returnNode);
reportCodeChange();
return returnNode;
}
boolean thenBranchIsExpressionBlock = isFoldableExpressBlock(thenBranch);
boolean elseBranchIsExpressionBlock = isFoldableExpressBlock(elseBranch);
if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) {
Node thenOp = getBlockExpression(thenBranch).getFirstChild();
Node elseOp = getBlockExpression(elseBranch).getFirstChild();
if (thenOp.getType() == elseOp.getType()) {
// if(x)a=1;else a=2; -> a=x?1:2;
if (NodeUtil.isAssignmentOp(thenOp)) {
Node lhs = thenOp.getFirstChild();
if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) &&
// if LHS has side effects, don't proceed [since the optimization
// evaluates LHS before cond]
// NOTE - there are some circumstances where we can
// proceed even if there are side effects...
!mayEffectMutableState(lhs)) {
n.removeChild(cond);
Node assignName = thenOp.removeFirstChild();
Node thenExpr = thenOp.removeFirstChild();
Node elseExpr = elseOp.getLastChild();
elseOp.removeChild(elseExpr);
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
Node assign = new Node(thenOp.getType(), assignName, hookNode)
.copyInformationFrom(thenOp);
Node expr = NodeUtil.newExpr(assign);
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
} else if (NodeUtil.isCall(thenOp)) {
// if(x)foo();else bar(); -> x?foo():bar()
n.removeChild(cond);
thenOp.detachFromParent();
elseOp.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenOp, elseOp)
.copyInformationFrom(n);
Node expr = NodeUtil.newExpr(hookNode);
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
}
return n;
}
boolean thenBranchIsVar = isVarBlock(thenBranch);
boolean elseBranchIsVar = isVarBlock(elseBranch);
// if(x)var y=1;else y=2 -> var y=x?1:2
if (thenBranchIsVar && elseBranchIsExpressionBlock &&
NodeUtil.isAssign(getBlockExpression(elseBranch).getFirstChild())) {
Node var = getBlockVar(thenBranch);
Node elseAssign = getBlockExpression(elseBranch).getFirstChild();
Node name1 = var.getFirstChild();
Node maybeName2 = elseAssign.getFirstChild();
if (name1.hasChildren()
&& maybeName2.getType() == Token.NAME
&& name1.getString().equals(maybeName2.getString())) {
Node thenExpr = name1.removeChildren
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>();
Node elseExpr = elseAssign.getLastChild().detachFromParent();
cond.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
var.detachFromParent();
name1.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
// if(x)y=1;else var y=2 -> var y=x?1:2
} else if (elseBranchIsVar && thenBranchIsExpressionBlock &&
NodeUtil.isAssign(getBlockExpression(thenBranch).getFirstChild())) {
Node var = getBlockVar(elseBranch);
Node thenAssign = getBlockExpression(thenBranch).getFirstChild();
Node maybeName1 = thenAssign.getFirstChild();
Node name2 = var.getFirstChild();
if (name2.hasChildren()
&& maybeName1.getType() == Token.NAME
&& maybeName1.getString().equals(name2.getString())) {
Node thenExpr = thenAssign.getLastChild().detachFromParent();
Node elseExpr = name2.removeChildren();
cond.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
var.detachFromParent();
name2.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
}
return n;
}
/**
* Try to remove duplicate statements from IF blocks. For example:
*
* if (a) {
* x = 1;
* return true;
* } else {
* x = 2;
* return true;
* }
*
* becomes:
*
* if (a) {
* x = 1;
* } else {
* x = 2;
* }
* return true;
*
* @param n The IF node to examine.
*/
private void tryRemoveRepeatedStatements(Node n) {
Preconditions.checkState(n.getType() == Token.IF);
Node parent = n.getParent();
if (!NodeUtil.isStatementBlock(parent)) {
// If the immediate parent is something like a label, we
// can't move the statement, so bail.
return;
}
Node cond = n.getFirstChild();
Node trueBranch = cond.getNext();
Node falseBranch = trueBranch.getNext();
Preconditions.checkNotNull(trueBranch);
Preconditions.checkNotNull(falseBranch);
while (true) {
Node lastTrue = trueBranch.getLastChild();
Node lastFalse = falseBranch.getLastChild();
* @return Whether the node is a block with a single statement that is
* an return.
*/
private boolean isReturnExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node first = n.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>getFirstChild();
if (first.getType() == Token.RETURN) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return The expression that is part of the return.
*/
private Node getBlockReturnExpression(Node n) {
Preconditions.checkState(isReturnExpressBlock(n));
return n.getFirstChild().getFirstChild();
}
/**
* @return Whether the node is a block with a single statement that is
* a VAR declaration of a single variable.
*/
private boolean isVarBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
if (first.getType() == Token.VAR) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return The var node.
*/
private Node getBlockVar(Node n) {
Preconditions.checkState(isVarBlock(n));
return n.getFirstChild();
}
/**
* Does a statement consume a 'dangling else'? A statement consumes
* a 'dangling else' if an 'else' token following the statement
* would be considered by the parser to be part of the statement.
*/
private boolean consumesDanglingElse(Node n) {
while (true) {
switch (n.getType()) {
case Token.IF:
if (n.getChildCount() < 3) {
return true;
}
// This IF node has no else clause.
n = n.getLastChild();
continue;
case Token.WITH:
case Token.WHILE:
case Token.FOR:
n = n.getLastChild();
continue;
default:
return false;
}
}
}
/**
* Does the expression contain an operator with lower precedence than
* the argument?
*/
private boolean isLowerPrecedenceInExpression(Node n,
final int precedence) {
Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return NodeUtil.precedence(input.getType()) < precedence;
}
};
return NodeUtil.has(n, isLowerPrecedencePredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Does the expression contain a property assignment?
*/
private boolean isPropertyAssignmentInExpression(Node n) {
Predicate<Node> isPropertyAssignmentInExpressionPredicate =
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return (input.getType() == Token.GETPROP &&
input.getParent().getType() == Token.ASSIGN);
}
};
return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Try to minimize conditions expressions, as there are additional
* assumptions that can be made when it is known that the final result
* is
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> a boolean.
*
* The following transformations are done recursively:
* !(x||y) --> !x&&!y
* !(x&&y) --> !x||!y
* !!x --> x
* Thus:
* !(x&&!y) --> !x||!!y --> !x||y
*
* Returns the replacement for n, or the original if no change was made
*/
private Node tryMinimizeCondition(Node n) {
Node parent = n.getParent();
switch (n.getType()) {
case Token.NOT:
Node first = n.getFirstChild();
switch (first.getType()) {
case Token.NOT: {
Node newRoot = first.removeFirstChild();
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// NOT children are handled below.
return newRoot;
}
case Token.AND:
case Token.OR: {
Node leftParent = first.getFirstChild();
Node rightParent = first.getLastChild();
if (leftParent.getType() == Token.NOT
&& rightParent.getType() == Token.NOT) {
Node left = leftParent.removeFirstChild();
Node right = rightParent.removeFirstChild();
int newOp = (first.getType() == Token.AND) ? Token.OR : Token.AND;
Node newRoot = new Node(newOp, left, right);
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// AND and OR children below.
return newRoot;
}
}
break;
}
// No need to traverse, tryMinimizeCondition is called on the NOT
// children in the general case in the main post-order traversal.
return n;
case Token.OR:
case Token.AND: {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// Because the expression is in a boolean context minimize
// the children, this can't be done in the general case.
left = tryMinimizeCondition(left);
right = tryMinimizeCondition(right);
// Remove useless conditionals
// Handle four cases:
// x || false --> x
// x || true --> true
// x && true --> x
// x && false --> false
TernaryValue rightVal = NodeUtil.getBooleanValue(right);
if (NodeUtil.getBooleanValue(right) != TernaryValue.UNKNOWN) {
int type = n.getType();
Node replacement = null;
boolean rval = rightVal.toBoolean(true);
// (x || FALSE) => x
// (x && TRUE) => x
if (type == Token.OR && !rval ||
type == Token.AND && rval) {
replacement = left;
} else if (!mayHaveSideEffects(left)) {
replacement = right;
}
if (replacement != null) {
n.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>detachChildren();
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
}
return n;
}
case Token.HOOK: {
Node condition = n.getFirstChild();
Node trueNode = n.getFirstChild().getNext();
Node falseNode = n.getLastChild();
// Because the expression is in a boolean context minimize
// the result children, this can't be done in the general case.
// The condition is handled in the general case in #optimizeSubtree
trueNode = tryMinimizeCondition(trueNode);
falseNode = tryMinimizeCondition(falseNode);
// Handle four cases:
// x ? true : false --> x
// x ? false : true --> !x
// x ? true : y --> x || y
// x ? y : false --> x && y
Node replacement = null;
if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE
&& NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = condition;
} else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.FALSE
&& NodeUtil.getBooleanValue(falseNode) == TernaryValue.TRUE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = new Node(Token.NOT, condition);
} else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE) {
// Remove useless true case.
n.detachChildren();
replacement = new Node(Token.OR, condition, falseNode);
} else if (NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) {
// Remove useless false case
n.detachChildren();
replacement = new Node(Token.AND, condition, trueNode);
}
if (replacement != null) {
parent.replaceChild(n, replacement);
n = replacement;
reportCodeChange();
}
return n;
}
default:
// while(true) --> while(1)
TernaryValue nVal = NodeUtil.getBooleanValue(n);
if (nVal != TernaryValue.UNKNOWN) {
boolean result = nVal.toBoolean(true);
int equivalentResult = result ? 1 : 0;
return maybeReplaceChildWithNumber(n, parent, equivalentResult);
}
// We can't do anything else currently.
return n;
}
}
/**
* Replaces a node with a number node if the new number node is not equivalent
* to the current node.
*
* Returns the replacement for n if it was replaced, otherwise returns n.
*/
private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) {
Node newNode = Node.newNumber(num);
if (!newNode.isEquivalentTo(n)) {
parent.replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
return n;
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> }
private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS =
// String, Number, and Boolean functions return non-object types, whereas
// new String, new Number, and new Boolean return object types, so don't
// include them here.
ImmutableSet.of(
"Object",
"Array",
"RegExp",
"Error"
);
/**
* Fold "new Object()" to "Object()".
*/
private Node tryFoldStandardConstructors(Node n) {
Preconditions.checkState(n.getType() == Token.NEW);
// If name normalization has been run then we know that
// new Object() does in fact refer to what we think it is
// and not some custom-defined Object().
if (isASTNormalized()) {
if (n.getFirstChild().getType() == Token.NAME) {
String className = n.getFirstChild().getString();
if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) {
n.setType(Token.CALL);
reportCodeChange();
}
}
}
return n;
}
/**
* Replaces a new Array or Object node with an object literal, unless the
* call to Array or Object is to a local function with the same name.
*/
private Node tryFoldLiteralConstructor(Node n) {
Preconditions.checkArgument(n.getType() == Token.CALL
|| n.getType() == Token.NEW);
Node constructorNameNode = n.getFirstChild();
Node newLiteralNode = null;
// We require the AST to be normalized to ensure that, say,
// Object() really refers to the built-in Object constructor
// and not a user-defined constructor with the same name.
if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) {
String className = constructorNameNode.getString();
if ("RegExp".equals(className)) {
// "RegExp("boo", "g")" --> /boo/g
return tryFoldRegularExpressionConstructor(n);
} else {
boolean constructorHasArgs = constructorNameNode.getNext() != null;
if ("Object".equals(className) && !constructorHasArgs) {
// "Object()" --> "{}"
newLiteralNode = new Node(Token.OBJECTLIT);
} else if ("Array".equals(className)) {
// "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]"
Node arg0 = constructorNameNode.getNext();
FoldArrayAction action = isSafeToFoldArrayConstructor(arg0);
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS ||
action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) {
newLiteralNode = new Node(Token.ARRAYLIT);
n.removeChildren();
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) {
newLiteralNode.addChildrenToFront(arg0);
}
}
}
if (newLiteralNode != null) {
n.getParent().replaceChild(n,
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> newLiteralNode);
reportCodeChange();
return newLiteralNode;
}
}
}
return n;
}
private static enum FoldArrayAction {
NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS}
/**
* Checks if it is safe to fold Array() constructor into []. It can be
* obviously done, if the initial constructor has either no arguments or
* at least two. The remaining case may be unsafe since Array(number)
* actually reserves memory for an empty array which contains number elements.
*/
private FoldArrayAction isSafeToFoldArrayConstructor(Node arg) {
FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD;
if (arg == null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
} else if (arg.getNext() != null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
} else {
switch (arg.getType()) {
case (Token.STRING):
// "Array('a')" --> "['a']"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
case (Token.NUMBER):
// "Array(0)" --> "[]"
if (arg.getDouble() == 0) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
}
break;
case (Token.ARRAYLIT):
// "Array([args])" --> "[[args]]"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
default:
}
}
return action;
}
private Node tryFoldRegularExpressionConstructor(Node n) {
Node parent = n.getParent();
Node constructor = n.getFirstChild();
Node pattern = constructor.getNext(); // e.g. ^foobar$
Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi
// Only run on normalized AST to make sure RegExp() is actually
// the RegExp we expect (if the AST has been normalized then
// other RegExp's will have been renamed to something like RegExp$1)
if (!isASTNormalized()) {
return n;
}
if (null == pattern || (null != flags && null != flags.getNext())) {
// too few or too many arguments
return n;
}
if (// is pattern folded
pattern.getType() == Token.STRING
// make sure empty pattern doesn't fold to //
&& !"".equals(pattern.getString())
// NOTE(nicksantos): Make sure that the regexp isn't longer than
// 100 chars, or it blows up the regexp parser in Opera 9.2.
&& pattern.getString().length() < 100
&& (null == flags || flags.getType() == Token.STRING)
// don't escape patterns with unicode escapes since Safari behaves badly
// (read can't parse or crashes) on regex
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> literals with unicode escapes
&& !containsUnicodeEscape(pattern.getString())) {
// Make sure that / is escaped, so that it will fit safely in /brackets/.
// pattern is a string value with \\ and similar already escaped
pattern = makeForwardSlashBracketSafe(pattern);
Node regexLiteral;
if (null == flags || "".equals(flags.getString())) {
// fold to /foobar/
regexLiteral = new Node(Token.REGEXP, pattern);
} else {
// fold to /foobar/gi
if (!areValidRegexpFlags(flags.getString())) {
error(INVALID_REGULAR_EXPRESSION_FLAGS, flags);
return n;
}
if (!areSafeFlagsToFold(flags.getString())) {
return n;
}
n.removeChild(flags);
regexLiteral = new Node(Token.REGEXP, pattern, flags);
}
parent.replaceChild(n, regexLiteral);
reportCodeChange();
return regexLiteral;
}
return n;
}
private static final Pattern REGEXP_FLAGS_RE = Pattern.compile("^[gmi]*$");
/**
* are the given flags valid regular expression flags?
* Javascript recognizes several suffix flags for regular expressions,
* 'g' - global replace, 'i' - case insensitive, 'm' - multi-line.
* They are case insensitive, and javascript does not recognize the extended
* syntax mode, single-line mode, or expression replacement mode from perl5.
*/
private static boolean areValidRegexpFlags(String flags) {
return REGEXP_FLAGS_RE.matcher(flags).matches();
}
/**
* are the given flags safe to fold?
* We don't fold the regular expression if global ('g') flag is on,
* because in this case it isn't really a constant: its 'lastIndex'
* property contains the state of last execution, so replacing
* 'new RegExp('foobar','g')' with '/foobar/g' may change the behavior of
* the program if the RegExp is used inside a loop, for example.
*/
private static boolean areSafeFlagsToFold(String flags) {
return flags.indexOf('g') < 0;
}
/**
* returns a string node that can safely be rendered inside /brackets/.
*/
private static Node makeForwardSlashBracketSafe(Node n) {
String s = n.getString();
// sb contains everything in s[0:pos]
StringBuilder sb = null;
int pos = 0;
for (int i = 0; i < s.length(); ++i) {
switch (s.charAt(i)) {
case '\\': // skip over the next char after a '\\'.
++i;
break;
case '/': // escape it
if (null == sb) { sb = new StringBuilder(s.length() + 16); }
sb.append(s, pos, i).append('\\');
pos = i;
break;
}
}
// don't discard useful line-number info if there were no changes
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Body();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.getType() == Token.BLOCK);
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
}
static class CompactCodePrinter
extends MappedCodePrinter {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gzipped, it makes sense to try to make the newlines appear in similar
// contexts so that GZIP can encode them for 'free'.
//
// This version tries to break the lines at 'preferred' places, which are
// between the top-level forms. This works because top level forms tend to
// be more uniform than arbitary legal contexts. Better compression would
// probably require explicit modelling of the gzip algorithm.
private final boolean lineBreak;
private int lineStartPosition = 0;
private int preferredBreakPosition = 0;
/**
* @param lineBreak break the lines a bit more aggressively
* @param lineLengthThreshold The length of a line after which we force
* a newline when possible.
* @param createSrcMap Whether to gather source position
* mapping information when printing.
* @param sourceMapDetailLevel A filter to control
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final
Function<TypePair, TypePair> INEQ =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) {
typeOfNode = left;
stringNode = right;
} else if (right.getType() == Token.TYPEOF &&
left.getType() == Token.STRING) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().getType() == Token.STRING) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
blindScope, merging);
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for non side effecting statements such as
* <pre>
* var s = "this string is "
* "continued on the next line but you forgot the +";
* x == foo(); // should that be '='?
* foo();; // probably just a stray-semicolon. Doesn't hurt to check though
* </p>
* and generates warnings.
*
*/
final class CheckSideEffects extends AbstractPostOrderCallback {
static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning(
"JSC_USELESS_CODE",
"Suspicious code. {0}");
private final CheckLevel level;
CheckSideEffects(CheckLevel level) {
this.level = level;
}
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.getType() == Token.EMPTY ||
n.getType() == Token.COMMA) {
return;
}
if (parent == null)
return;
int pt = parent.getType();
if (pt == Token.COMMA) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.CALL &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval.
if (n == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
n.getNext().getType() == Token.NAME &&
"eval".equals(n.getNext().getString())) {
return;
}
}
if (
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>n == parent.getLastChild()) {
for (Node an : parent.getAncestors()) {
int ancestorType = an.getType();
if (ancestorType == Token.COMMA)
continue;
if (ancestorType != Token.EXPR_RESULT &&
ancestorType != Token.BLOCK)
return;
else
break;
}
}
} else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
if (pt == Token.FOR && parent.getChildCount() == 4 &&
(n == parent.getFirstChild() ||
n == parent.getFirstChild().getNext().getNext())) {
// Fall through and look for warnings for the 1st and 3rd child
// of a for.
} else {
return; // it might be ok to not have a side-effect
}
}
if (NodeUtil.isSimpleOperatorType(n.getType()) ||
!NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
return;
} else if (NodeUtil.isExpressionNode(n)) {
// we already reported the problem when we visited the child.
return;
}
String msg = "This code lacks side-effects. Is there a bug?";
if (n.getType() == Token.STRING) {
msg = "Is there a missing '+' on the previous line?";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> /**
* {@inheritDoc}
*
* <p>This enforces Google's convention about enum key names. They must match
* the regular expression {@code [A-Z0-9][A-Z0-9_]*}.
*
* <p>Examples:
* <ul>
* <li>A</li>
* <li>213</li>
* <li>FOO_BAR</li>
* </ul>
*/
@Override
public boolean isValidEnumKey(String key) {
return ENUM_KEY_PATTERN.matcher(key).matches();
}
/**
* {@inheritDoc}
*
* <p>In Google code, parameter names beginning with {@code opt_} are
* treated as optional arguments.
*/
@Override
public boolean isOptionalParameter(Node parameter) {
return parameter.getString().startsWith(OPTIONAL_ARG_PREFIX);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return VAR_ARGS_NAME.equals(parameter.getString());
}
/**
* {@inheritDoc}
*
* <p>In Google code, any global name starting with an underscore is
* considered exported.
*/
@Override
public boolean isExported(String name, boolean local) {
return !local && name.startsWith("_");
}
/**
* {@inheritDoc}
*
* <p>In Google code, private names end with an underscore, and exported
* names are never considered private (see {@link #isExported}).
*/
@Override
public boolean isPrivate(String name) {
return name.endsWith("_") && !isExported(name);
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.parsing;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.mozilla.rhino.CompilerEnvirons;
import com.google.javascript.jscomp.mozilla.rhino.Context;
import com.google.javascript.jscomp.mozilla.rhino.ErrorReporter;
import com.google.javascript.jscomp.mozilla.rhino.EvaluatorException;
import com.google.javascript.jscomp.mozilla.rhino.Parser;
import com.google.javascript.jscomp.mozilla.rhino.ast.AstRoot;
import com.google.javascript.rhino.Node;
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;
public class ParserRunner {
private static final String configResource =
"com.google.javascript.jscomp.parsing.ParserConfig";
private static Set<String> annotationNames = null;
private static Set<String> suppressionNames = null;
// Should never need to instantiate class of static methods.
private ParserRunner() {}
@Deprecated
public static Config createConfig(boolean isIdeMode) {
return createConfig(isIdeMode, false);
}
public static Config createConfig(boolean isIdeMode, boolean isES5Mode) {
initResourceConfig();
return new Config(annotationNames, suppressionNames, isIdeMode, isES5Mode);
}
private static synchronized void initResourceConfig() {
if (annotationNames != null) {
return;
}
ResourceBundle config = ResourceBundle.getBundle(configResource);
annotationNames = extractList(config.getString("jsdoc.annotations"));
suppressionNames = extractList(config.getString("jsdoc.suppressions"));
}
private static Set<String> extractList(String configProp) {
String[] names = configProp.split(",");
Set<String> trimmedNames = Sets.newHashSet();
for (String name : names) {
trimmedNames.add(name.trim());
}
return ImmutableSet.copyOf(trimmedNames);
}
/**
* Parses the JavaScript text given by a reader.
*
* @param sourceName The filename.
* @param sourceString Source code from the
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
if (n.getType() == Token.EXPR_VOID) {
n.setType(Token.EXPR_RESULT);
reportChange();
}
// Remove unused properties to minimize differences between ASTs
// produced by the two parsers.
if (n.getType() == Token.FUNCTION) {
Preconditions.checkState(n.getProp(
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Node.FUNCTION_PROP) == null);
}
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.OBJECTLIT) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.getType() == Token.NAME &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.getType() == Token.ASSIGN) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
if (key.getJSDocInfo() != null &&
value.getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> }
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
if (shouldReport) {
compiler.report(
t.makeError(n, INVALID_CAST,
castType.toString(), type.toString()));
}
registerMismatch(type, castType);
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
*/
void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var,
String variableName, JSType newType) {
boolean allowDupe = false;
if (n.getType() == Token.GETPROP) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type.
if (var.input == null) {
n.setJSType(varType);
if (parent.getType() == Token.VAR) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.getType() == Token.FUNCTION);
parent.setJSType(varType);
}
} else {
// Always
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().getType() == Token.EXPR_RESULT) ||
!newType.equals(varType)) {
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented.
*/
void expectAllInterfacePropertiesImplemented(FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
if (!instance.hasProperty(prop)) {
Node source = type.getSource();
Preconditions.checkNotNull(source);
String sourceName = (String) source.getProp(Node.SOURCENAME_PROP);
sourceName = sourceName == null ? "" : sourceName;
if (shouldReport) {
compiler.report(JSError.make(sourceName, source,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implemented.toString(), instance.toString()));
}
registerMismatch(instance, implemented);
}
}
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found, getNativeType(required));
}
private void mismatch(String sourceName, Node n,
String msg, JSType found, JSType required) {
registerMismatch(found, required);
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, TYPE_MISMATCH_WARNING,
formatFoundRequired(msg, found, required)));
}
}
private void registerMismatch(JSType found, JSType required) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (found.canAssignTo(required) || required.canAssignTo(found)) {
return;
}
mismatches.add(new TypeMismatch(found, required));
if (found instanceof FunctionType &&
required instanceof FunctionType) {
FunctionType fnTypeA = ((FunctionType) found);
FunctionType fnTypeB
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> = ((FunctionType) required);
Iterator<Node> paramItA = fnTypeA.getParameters().iterator();
Iterator<Node> paramItB = fnTypeB.getParameters().iterator();
while (paramItA.hasNext() && paramItB.hasNext()) {
registerIfMismatch(paramItA.next().getJSType(),
paramItB.next().getJSType());
}
registerIfMismatch(fnTypeA.getReturnType(), fnTypeB.getReturnType());
}
}
private void registerIfMismatch(JSType found, JSType required) {
if (found != null && required != null &&
!found.canAssignTo(required)) {
registerMismatch(found, required);
}
}
/**
* Formats a found/required error message.
*/
private String formatFoundRequired(String description, JSType found,
JSType required) {
return MessageFormat.format(FOUND_REQUIRED, description, found, required);
}
/**
* Given a node, get a human-readable name for the type of that node so
* that will be easy for the programmer to find the original declaration.
*
* For example, if SubFoo's property "bar" might have the human-readable
* name "Foo.prototype.bar".
*
* @param n The node.
* @param dereference If true, the type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was originally defined.
if (n.getType() == Token.GETPROP) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(type.toObjectType() != null &&
type.toObjectType().getConstructor() != null)) {
return type.toString();
} else if (qualifiedName != null) {
return qualifiedName;
} else if (type instanceof FunctionType) {
// Don't show complex function names.
return "function";
} else {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
if (name.declaration != null) {
allRefInfo.put(name.declaration.node,
new RefInfo(name.declaration, name));
}
if (name.refs != null) {
for (Ref ref : name.refs) {
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.fullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.getType() == Token.ASSIGN && name.isSimpleName() &&
name.declaration == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (processDefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> no warning is expected
*/
public void testSame(String[] js, DiagnosticType error, DiagnosticType warning) {
test(js, js, error, warning);
}
/**
* Verifies that the compiler pass's JS output is the same as the input.
*
* @param modules Module inputs
*/
public void testSame(JSModule[] modules) {
testSame(modules, null);
}
/**
* Verifies that the compiler pass's JS output is the same as the input.
*
* @param modules Module inputs
* @param warning A warning, or null for no expected warning.
*/
public void testSame(JSModule[] modules, DiagnosticType warning) {
try {
String[] expected = new String[modules.length];
for (int i = 0; i < modules.length; i++) {
expected[i] = "";
for (CompilerInput input : modules[i].getInputs()) {
expected[i] += input.getSourceFile().getCode();
}
}
test(modules, expected, null, warning);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param compiler A compiler that has been initialized via
* {@link Compiler#init}
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
*/
protected void test(Compiler compiler, String[] expected,
DiagnosticType error, DiagnosticType warning) {
test(compiler, expected, error, warning, null);
}
/**
* Verifies that the compiler pass's JS output matches the expected output
* and (optionally) that an expected warning is issued. Or, if an error is
* expected, this method just verifies that the error is encountered.
*
* @param compiler A compiler that has been initialized via
* {@link Compiler#init}
* @param expected Expected output, or null if an error is expected
* @param error Expected error, or null if no error is expected
* @param warning Expected warning, or null if no warning is expected
* @param description The description of the expected warning,
* or null if no warning is expected or if the warning's description
* should not be examined
*/
private void test(Compiler compiler, String[] expected,
DiagnosticType error, DiagnosticType warning,
String description) {
RecentChange recentChange = new RecentChange();
compiler.addChangeHandler(recentChange);
Node root = compiler.parseInputs();
assertTrue("Unexpected parse error(s): " +
Joiner.on("\n").join(compiler.getErrors()), root != null);
Node externsRoot = root.getFirstChild();
Node mainRoot = root.getLastChild();
// Save the tree
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> for later comparison.
Node rootClone = root.cloneTree();
Node externsRootClone = rootClone.getFirstChild();
Node mainRootClone = rootClone.getLastChild();
int numRepetitions = getNumRepetitions();
ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
int aggregateWarningCount = 0;
List<JSError> aggregateWarnings = Lists.newArrayList();
boolean hasCodeChanged = false;
assertFalse("Code should not change before processing",
recentChange.hasCodeChanged());
for (int i = 0; i < numRepetitions; ++i) {
if (compiler.getErrorCount() == 0) {
errorManagers[i] = new BlackHoleErrorManager(compiler);
// Only run the type checking pass once, if asked.
// Running it twice can cause unpredictable behavior because duplicate
// objects for the same type are created, and the type system
// uses reference equality to compare many types.
if (typeCheckEnabled && i == 0) {
TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
check.processForTesting(externsRoot, mainRoot);
}
// Only run the normalize pass once, if asked.
if (normalizeEnabled && i == 0) {
normalizeActualCode(compiler, externsRoot, mainRoot);
}
if (markNoSideEffects && i == 0) {
MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
mark.process(externsRoot, mainRoot);
}
recentChange.reset();
getProcessor(compiler).process(externsRoot, mainRoot);
if (checkLineNumbers) {
(new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
}
hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
aggregateWarningCount += errorManagers[i].getWarningCount();
aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings()));
if (normalizeEnabled) {
boolean verifyDeclaredConstants = true;
new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
.process(externsRoot, mainRoot);
}
}
}
if (error == null) {
assertEquals(
"Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()),
0, compiler.getErrorCount());
// Verify the symbol table.
ErrorManager symbolTableErrorManager =
new BlackHoleErrorManager(compiler);
Node expectedRoot = parseExpectedJs(expected);
expectedRoot.detachFromParent();
JSError[] stErrors = symbolTableErrorManager.getErrors();
if (expectedSymbolTableError != null) {
assertEquals("There should be one error.", 1, stErrors.length);
assertEquals(expectedSymbolTableError, stErrors[0].getType());
} else {
assertEquals("Unexpected symbol table error(s): " +
Joiner.on("\n").join(stErrors),
0, stErrors.length);
}
if (warning == null) {
assertEquals(
"Unexpected warning
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>(s): " + Joiner.on("\n").join(aggregateWarnings),
0, aggregateWarningCount);
} else {
assertEquals("There should be one warning, repeated " + numRepetitions +
" time(s).", numRepetitions, aggregateWarningCount);
for (int i = 0; i < numRepetitions; ++i) {
JSError[] warnings = errorManagers[i].getWarnings();
JSError actual = warnings[0];
assertEquals(warning, actual.getType());
// Make sure that source information is always provided.
if (!allowSourcelessWarnings) {
assertTrue("Missing source file name in warning",
actual.sourceName != null && !actual.sourceName.isEmpty());
assertTrue("Missing line number in warning",
-1 != actual.lineNumber);
assertTrue("Missing char number in warning",
-1 != actual.getCharno());
}
if (description != null) {
assertEquals(description, actual.description);
}
}
}
if (normalizeEnabled) {
normalizeActualCode(compiler, externsRootClone, mainRootClone);
}
if (mainRootClone.checkTreeEqualsSilent(mainRoot)) {
assertFalse(
"compiler.reportCodeChange() was called " +
"even though nothing changed",
hasCodeChanged);
} else {
assertTrue("compiler.reportCodeChange() should have been called",
hasCodeChanged);
}
if (compareAsTree) {
String explanation = expectedRoot.checkTreeEquals(mainRoot);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
} else if (expected != null) {
assertEquals(
Joiner.on("").join(expected), compiler.toSource(mainRoot));
}
// Verify normalization is not invalidated.
Node normalizeCheckRootClone = root.cloneTree();
Node normalizeCheckExternsRootClone = root.getFirstChild();
Node normalizeCheckMainRootClone = root.getLastChild();
new PrepareAst(compiler).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
String explanation =
normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
assertNull("Node structure normalization invalidated.\nExpected: " +
compiler.toSource(normalizeCheckMainRootClone) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
// TODO(johnlenz): enable this for most test cases.
// Currently, this invalidates test for while-loops, for-loop
// initializers, and other naming. However, a set of code
// (FoldConstants, etc) runs before the Normalize pass, so this can't be
// force on everywhere.
if (normalizeEnabled) {
new Normalize(compiler, true).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> = null;
// Vars that still need to be declared in externs. These will be declared
// at the end of the pass, or when we see the equivalent var declared
// in the normal code.
private Set<String> varsToDeclareInExterns = Sets.newHashSet();
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.getType() == Token.VAR ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
parent.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.LP:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput =
compiler.newExternInput("{SyntheticVarsDeclar}");
}
return synthesizedExternsInput;
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> uses reference equality, so it's necessary to
* inject a scope when you traverse.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
public Set<Var> getReferencedVariables() {
return referenceMap.keySet();
}
/**
* Gets the reference collection for the given variable.
*/
public ReferenceCollection getReferenceCollection(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
Var v = t.getScope().getVar(n.getString());
if (v != null && varFilter.apply(v)) {
addReference(t, v,
new Reference(n, parent, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block statck and invokes any additional behavior.
*/
public void exitScope(NodeTraversal t) {
blockStack.pop();
behavior.afterExitScope(t, referenceMap);
}
/**
* Updates block stack.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explcit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null)
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(qualifiedName, origType, type);
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
public JSType caseFunctionType(FunctionType type) {
return type;
}
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
public JSType caseObjectType(ObjectType type) {
return type;
}
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
public JSType caseVoidType() {
return null;
}
};
/**
* @see #getRestrictedWithoutNull(JSType)
*/
private final Visitor<JSType> restrictNullVisitor =
new Visitor<JSType
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, NUMBER_TYPE);
} else {
typeable = false;
}
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.STRING:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, STRING_TYPE);
} else {
typeable = false;
}
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else if (leftType.isNumber()) {
validator.expectNumber(
t, n, rightType, "right side of numeric comparison");
} else if (leftType.matchesNumberContext() &&
rightType.matchesNumberContext()) {
// OK.
} else {
// Whether the comparison is numeric will be determined at runtime
// each time the expression is evaluated. Regardless, both operands
// should match a string context.
String message = "left side of comparison";
validator.expectString(t, n, leftType, message);
validator.expectNotVoid(
t, n, leftType, message, getNativeType(STRING_TYPE));
message = "right side of comparison";
validator.expectString(t, n, rightType, message
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>);
validator.expectNotVoid(
t, n, rightType, message, getNativeType(STRING_TYPE));
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.IN:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right);
validator.expectObject(t, n, rightType, "'in' requires an object");
validator.expectString(t, left, leftType, "left side of 'in'");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, leftType, "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
if (!isReference(n.getFirstChild())) {
report(t, n, BAD_DELETE);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
if (lvalue.getType() == Token.GETPROP) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.getType() == Token.GETPROP) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
// /** @enum ... */object.name = ...;
if (info != null && info.hasEnumParameterType()) {
checkEnumInitializer(
t, rvalue, info.getEnumParameterType().evaluate(
t.getScope(), typeRegistry));
return;
}
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) objectJsType;
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
}
} else {
// TODO(user): might want to flag that
}
return;
}
// object.prototype.property = ...;
if (object.getType() == Token.GETPROP) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = object2.getJSType();
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, getJSType(rvalue));
}
} else {
// TODO(user): might want to flag that
}
return;
}
}
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
type.getPropertyType(property), object, property);
}
return;
}
} else if (lvalue.getType() == Token.NAME) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getString());
if (var
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> != null) {
if (var.isTypeInferred()) {
return;
}
}
}
// fall through case
JSType leftType = getJSType(lvalue);
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* Returns true if any type in the chain has an implictCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// TODO(user): We're not 100% confident that type-checking works,
// so we return quietly if the unknown type is a superclass of this type.
// Remove this check as we become more confident. We should flag a warning
// when the unknown type is on the inheritance chain, as it is likely
// because of a programmer error.
if (ctorType.hasUnknownSupertype()) {
return;
}
FunctionType superClass = ctorType.getSuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getPrototype().hasProperty(propertyName);
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface : ctorType.getImplementedInterfaces()) {
if (implementedInterface.isUnknownType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty;
if (reportMissingOverride.isOn() && !declaredOverride &&
interfaceHasProperty) {
// @override not present, but the property does override an interface
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_INTERFACE_PROPERTY, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString()));
}
// Check that it is ok
if (interfaceHasProperty) {
JSType interfacePropType =
interfaceType.getPrototype().getPropertyType(propertyName);
if (!propertyType.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>canAssignTo(interfacePropType)) {
compiler.report(t.makeError(n,
HIDDEN_INTERFACE_PROPERTY_MISMATCH, propertyName,
interfaceType.getTopMostDefiningType(propertyName).toString(),
interfacePropType.toString(), propertyType.toString()));
}
}
}
}
if (!declaredOverride && !superClassHasProperty) {
// nothing to do here, it's just a plain new property
return;
}
JSType topInstanceType = superClassHasProperty ?
superClass.getTopMostDefiningType(propertyName) : null;
if (reportMissingOverride.isOn() && ctorType.isConstructor() &&
!declaredOverride && superClassHasProperty) {
// @override not present, but the property does override a superclass
// property
compiler.report(t.makeError(n, reportMissingOverride,
HIDDEN_SUPERCLASS_PROPERTY, propertyName,
topInstanceType.toString()));
}
if (!declaredOverride) {
// there's no @override to check
return;
}
// @override is present and we have to check that it is ok
if (superClassHasProperty) {
// there is a superclass implementation
JSType superClassPropType =
superClass.getPrototype().getPropertyType(propertyName);
if (!propertyType.canAssignTo(superClassPropType)) {
compiler.report(
t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
propertyName, topInstanceType.toString(),
superClassPropType.toString(), propertyType.toString()));
}
} else if (!foundInterfaceProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.LP ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.getType() == Token.ASSIGN) {
return;
}
// obj.prop or obj.method()
// Lots of types can appear
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotVoid(t, n, childType,
"undefined has no properties", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Make sure that the access of this property is ok.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
ObjectType objectType = childType.dereference();
if (objectType != null) {
JSType propType = getJSType(n);
if ((!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) &&
propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
} else {
// TODO(nicksantos): might want to flag the access on a non object when
// it's impossible to get a property from this type.
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().getType() == Token.OR &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
if (info != null && info.hasEnumParameterType()) {
// var.getType() can never be null, this would indicate a bug in the
// scope creation logic.
checkEnumInitializer(
t, value,
info.getEnumParameterType().evaluate(t.getScope(), typeRegistry));
} else if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
FunctionType type = getFunctionType(constructor);
if (type != null && type.isConstructor()) {
visitParameterList(t, n, type);
ensureTyped(t, n, type.getInstanceType());
} else {
// TODO(user): add support for namespaced objects.
if (constructor.getType() != Token.GETPROP) {
// TODO(user): make the constructor node have lineno/charno
// and use constructor for a more precise error indication.
// It seems that GETPROP nodes are missing this information.
Node line;
if (constructor.getLineno() < 0 || constructor.getCharno()
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> < 0) {
line = n;
} else {
line = constructor;
}
report(t, line, NOT_A_CONSTRUCTOR);
}
ensureTyped(t, n);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
JSDocInfo info = n.getJSDocInfo();
FunctionType functionType = (FunctionType) n.getJSType();
String functionPrivateName = n.getFirstChild().getString();
if (functionType.isInterface() || functionType.isConstructor()) {
FunctionType baseConstructor = functionType.
getPrototype().getImplicitPrototype().getConstructor();
if (baseConstructor != null &&
baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
(baseConstructor.isConstructor() && functionType.isInterface() ||
baseConstructor.isInterface() && functionType.isConstructor())) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName));
}
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
}
}
if (functionType.isConstructor()) {
validator.expectAllInterfacePropertiesImplemented(functionType);
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType instanceof FunctionType) {
FunctionType functionType = (FunctionType) childType;
// Non-native constructors should never be called directly.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType()) {
report(t, n, CONSTRUCTOR_NOT
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks the initializer of an enum. An enum can be initialized with an
* object literal whose values must be subtypes of the declared enum element
* type, or by copying another enum.</p>
*
* <p>In the case of an enum copy, we verify that the enum element type of the
* enum
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> used for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Examples:</p>
* <pre>var myEnum = {FOO: ..., BAR: ...};
* var myEnum = myOtherEnum;</pre>
*
* @param value the value used for initialization of the enum
* @param primitiveType The type of each element of the enum.
*/
private void checkEnumInitializer(
NodeTraversal t, Node value, JSType primitiveType) {
if (value.getType() == Token.OBJECTLIT) {
for (Node key = value.getFirstChild();
key != null; key = key.getNext()) {
Node propValue = key.getFirstChild();
// the value's type must be assignable to the enum's primitive type
validator.expectCanAssignTo(
t, propValue, getJSType(propValue), primitiveType,
"element type must match enum's type");
}
} else if (value.getJSType() instanceof EnumType) {
// TODO(user): Remove the instanceof check in favor
// of a type.isEnumType() predicate. Currently, not all enum types are
// implemented by the EnumClass, e.g. the unknown type and the any
// type. The types need to be defined by interfaces such that an
// implementation can implement multiple types interface.
EnumType valueEnumType = (EnumType) value.getJSType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
primitiveType, "incompatible enum element types");
} else {
// The error condition is handled in TypedScopeCreator.
}
}
/**
* This predicate is used to determine if the node represents an expression
* that is a Reference according to JavaScript definitions.
*
* @param n The node being checked.
* @return true if the sub-tree n is a reference, false otherwise.
*/
private static boolean isReference(Node n) {
switch (n.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
return true;
default:
return false;
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
/**
* Gets the type of the node or {@code null} if the node's type is not a
* function.
*/
private Function
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Type getFunctionType(Node n) {
JSType type = getJSType(n).restrictByNotNullOrUndefined();
if (type.isUnknownType()) {
return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE);
} else if (type instanceof FunctionType) {
return (FunctionType) type;
} else {
return null;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type instanceof FunctionType ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.getType() == Token.GETPROP ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>(child);
skipIndexes[j] = i;
j++;
}
i++;
}
node.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
return processInfixExpression(assignmentNode);
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* Ecma-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) &&
n.getFirstChild().getType() == Token.STRING &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode)
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
int leftParamPos = callNode.getAbsolutePosition() + callNode.getLp();
node.setLineno(callNode.getLineno());
node.setCharno(position2charno(leftParamPos));
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction = false;
if (name == null) {
name = new Name();
name.setIdentifier("");
isUnnamedFunction = true;
}
Node node = newNode(Token.FUNCTION);
Node newName = transform(name);
if (isUnnamedFunction) {
// Old Rhino tagged the empty name node with the line number of the
// declaration.
newName.setLineno(functionNode.getLineno());
// TODO(bowdidge) Mark
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Node.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
@Override
Node processNewExpression(NewExpression exprNode) {
return processFunctionCall(exprNode);
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (!config.acceptES5) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
if (el.isGetter()) {
key.setType(Token.GET);
} else if (el.isSetter()) {
key.setType(Token.SET);
}
key.addChildToFront(transform(el.getRight()));
node.addChildToBack(key);
}
return node;
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return newNode(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
return Token.NOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITNOT:
return Token.BITNOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.POS:
return Token.POS;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEG:
return Token.NEG;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEW:
return Token.NEW;
case com.google.javascript.jscomp.mozilla.rhino.Token.DELPROP:
return Token.DELPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOF:
return Token.TYPEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETPROP:
return Token.GETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP:
return Token.SETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETELEM:
return Token.GETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM:
return Token.SETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.CALL:
return Token.CALL;
case com.google.javascript.jscomp.mozilla.rhino.Token.NAME:
return Token.NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER:
return Token.NUMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.STRING:
return Token.STRING;
case com.google.javascript.jscomp.mozilla.rhino.Token.NULL:
return Token.NULL;
case com.google.javascript.jscomp.mozilla.rhino.Token.THIS:
return Token.THIS;
case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE:
return Token.FALSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE:
return Token.TRUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ:
return Token.SHEQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE:
return Token.SHNE;
case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP:
return Token.REGEXP;
case com.google.javascript.jscomp.mozilla.rhino.Token.BINDNAME:
return Token.BINDNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.THROW:
return Token.THROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETHROW:
return Token.RETHROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.IN:
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>ino.Token.RESERVED:
return Token.RESERVED;
case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY:
return Token.EMPTY;
case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK:
return Token.BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL:
return Token.LABEL;
case com.google.javascript.jscomp.mozilla.rhino.Token.TARGET:
return Token.TARGET;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOOP:
return Token.LOOP;
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_VOID:
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT:
return Token.EXPR_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.JSR:
return Token.JSR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT:
return Token.SCRIPT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOFNAME:
return Token.TYPEOFNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.USE_STACK:
return Token.USE_STACK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP_OP:
return Token.SETPROP_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM_OP:
return Token.SETELEM_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_BLOCK:
return Token.LOCAL_BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF_OP:
return Token.SET_REF_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOTDOT:
return Token.DOTDOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLONCOLON:
return Token.COLONCOLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.XML:
return Token.XML;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOTQUERY:
return Token.DOTQUERY;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLATTR:
return Token.XMLATTR;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLEND:
return Token.XMLEND;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_OBJECT:
return Token.TO_OBJECT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_DOUBLE:
return Token.TO_DOUBLE;
case com.google.javascript.js
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>parent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> inputsByName.get(name);
}
@Override
public CompilerInput newExternInput(String name) {
if (inputsByName.containsKey(name)) {
throw new IllegalArgumentException("Conflicting externs name: " + name);
}
SourceAst ast = new SyntheticAst(name);
CompilerInput input = new CompilerInput(ast, name, true);
inputsByName.put(name, input);
externsRoot.addChildToFront(ast.getAstRoot(this));
return input;
}
/** Add a source input dynamically. Intended for incremental compilation. */
void addIncrementalSourceAst(JsAst ast) {
String sourceName = ast.getSourceFile().getName();
Preconditions.checkState(
getInput(sourceName) == null,
"Duplicate input of name " + sourceName);
inputsByName.put(sourceName, new CompilerInput(ast));
}
/**
* Replace a source input dynamically. Intended for incremental
* re-compilation.
*
* If the new source input doesn't parse, then keep the old input
* in the AST and return false.
*
* @return Whether the new AST was attached successfully.
*/
boolean replaceIncrementalSourceAst(JsAst ast) {
String sourceName = ast.getSourceFile().getName();
CompilerInput oldInput =
Preconditions.checkNotNull(
getInput(sourceName),
"No input to replace: " + sourceName);
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
Node oldRoot = oldInput.getAstRoot(this);
if (oldRoot != null) {
oldRoot.getParent().replaceChild(oldRoot, newRoot);
} else {
getRoot().getLastChild().addChildToBack(newRoot);
}
inputsByName.put(sourceName, new CompilerInput(ast));
return true;
}
@Override
JSModuleGraph getModuleGraph() {
return moduleGraph;
}
@Override
public JSTypeRegistry getTypeRegistry() {
if (typeRegistry == null) {
typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes);
}
return typeRegistry;
}
@Override
ScopeCreator getScopeCreator() {
return getPassConfig().getScopeCreator();
}
@Override
public Scope getTopScope() {
return getPassConfig().getTopScope();
}
@Override
public ReverseAbstractInterpreter getReverseAbstractInterpreter() {
if (abstractInterpreter == null) {
ChainableReverseAbstractInterpreter interpreter =
new SemanticReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry());
if (options.closurePass) {
interpreter = new ClosureReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry())
.append(interpreter).getFirst();
}
abstractInterpreter = interpreter;
}
return abstractInterpreter;
}
@Override
TypeValidator getTypeValidator() {
if (typeValidator == null) {
typeValidator = new TypeValidator(this);
}
return typeValidator;
}
//------------------------------------------------------------------------
// Parsing
//
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Node.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void on
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Redeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = false;
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
if (!allowDupe) {
compiler.report(
JSError.make(sourceName, n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(sourceName, n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
* @param declaredType The variable's type, according to JSDoc
*/
private void declareVar(Node n) {
Preconditions.checkState(n.getType() == Token.NAME);
CompilerInput input = compiler.getInput(sourceName);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.getType() == Token.BLOCK
|| parent.getType() == Token.SCRIPT)) {
if (n.getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n));
} else if (n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.getType() == Token.VAR
&& n.getFirstChild().getFirstChild() != null
&& n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
JSType type = node.getJSType();
if (type instanceof FunctionType) {
return getFunctionAnnotation(node);
} else if (type != null && !type.isUnknownType()
&& !type.isEmptyType() && !type.isVoidType() &&
!type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode) {
Preconditions.checkState(fn
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Node.getType() == Token.FUNCTION);
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = (FunctionType) fnNode.getJSType();
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} ");
sb.append(paramNode.getString());
sb.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) {
sb.append(" * @return {" + retType + "}\n");
}
// Constructor/interface
if (funType.isConstructor() || funType.isInterface()) {
FunctionType superConstructor = funType.getSuperClassConstructor();
if (superConstructor != null) {
ObjectType superInstance =
funType.getSuperClassConstructor().getInstanceType();
if (!superInstance.toString().equals("Object")) {
sb.append(" * @extends {" + superInstance + "}\n");
}
}
// Avoid duplicates, add implemented type to a set first
Set<String> interfaces = Sets.newTreeSet();
for (ObjectType interfaze : funType.getImplementedInterfaces()) {
interfaces.add(interfaze.toString());
}
for (String interfaze : interfaces) {
sb.append(" * @implements {" + interfaze + "}\n");
}
if (funType.isConstructor()) {
sb.append(" * @constructor\n");
} else if (funType.isInterface()) {
sb.append(" * @interface\n");
}
}
if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) {
sb.append(" * @javadispatch\n");
}
sb.append(" */\n");
return sb.toString();
}
/**
* Creates a JSDoc-suitable String representation the type of a parameter.
*
* @param parameterNode The parameter node.
*/
private String getParameterNodeJSDocType(Node parameterNode) {
JSType parameterType = parameterNode.getJSType();
String typeString;
// Emit unknown types as '*' (AllType) since '?' (UnknownType) is not
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS",
"Class {0} has been deprecated.");
static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS_REASON",
"Class {0} has been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
}
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.getType() == Token.ASSIGN) {
Node lValue = parent.getFirstChild();
if (lValue.isQualifiedName()) {
if (lValue.getType() == Token.GETPROP) {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> // We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return ((FunctionType) lValueType).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> */
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR ||
parent.getType() == Token.NEW) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.getType() == Token.NEW) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE &&
!t.getInput().getName().equals(docInfo.getSourceName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), docInfo.getSourceName()));
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = t.inGlobalScope() &&
parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
boolean sameInput =
t.getInput().getName().equals(docInfo.getSourceName());
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || ownerType.differsFrom(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadable
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>JSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) ||
n.getType() == Token.NEW)) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.getType() == Token.GETPROP && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> boolean addOptionalParams(JSType ...types) {
if (hasVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(registry.createOptionalType(type)).setOptionalArg(true);
}
return true;
}
/**
* Add variable arguments to the end of the parameter list.
* @return False if this is called after var args are added.
*/
public boolean addVarArgs(JSType type) {
if (hasVarArgs()) {
return false;
}
// There are two types of variable argument functions:
// 1) Programmer-defined var args
// 2) Native bottom types that can accept any argument.
// For the first one, "undefined" is a valid value for all arguments.
// For the second, we do not want to cast it up to undefined.
if (!type.isEmptyType()) {
type = registry.createOptionalType(type);
}
newParameter(type).setVarArgs(true);
return true;
}
/**
* Copies the parameter specification from the given node.
*/
public Node newParameterFromNode(Node n) {
Node newParam = newParameter(n.getJSType());
newParam.setVarArgs(n.isVarArgs());
newParam.setOptionalArg(n.isOptionalArg());
return newParam;
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
paramNode.setJSType(type);
root.addChildToBack(paramNode);
return paramNode;
}
public Node build() {
return root;
}
private boolean hasOptionalOrVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null &&
(lastChild.isOptionalArg() || lastChild.isVarArgs());
}
public boolean hasVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null && lastChild.isVarArgs();
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> && c <= 9) {
long v = c;
for (int i = 1; i != len; ++i) {
c = str.charAt(i) - '0';
if (!(0 <= c && c <= 9)) {
return -1;
}
v = 10 * v + c;
}
// Check for overflow
if ((v >>> 32) == 0) {
return v;
}
}
}
return -1;
}
static boolean isSpecialProperty(String s)
{
return s.equals("__proto__") || s.equals("__parent__");
}
// ------------------
// Statements
// ------------------
public static String getMessage0(String messageId)
{
return getMessage(messageId, null);
}
public static String getMessage1(String messageId, Object arg1)
{
Object[] arguments = {arg1};
return getMessage(messageId, arguments);
}
public static String getMessage2(
String messageId, Object arg1, Object arg2)
{
Object[] arguments = {arg1, arg2};
return getMessage(messageId, arguments);
}
public static String getMessage3(
String messageId, Object arg1, Object arg2, Object arg3)
{
Object[] arguments = {arg1, arg2, arg3};
return getMessage(messageId, arguments);
}
public static String getMessage4(
String messageId, Object arg1, Object arg2, Object arg3, Object arg4)
{
Object[] arguments = {arg1, arg2, arg3, arg4};
return getMessage(messageId, arguments);
}
/* OPT there's a noticable delay for the first error! Maybe it'd
* make sense to use a ListResourceBundle instead of a properties
* file to avoid (synchronized) text parsing.
*/
public static String getMessage(String messageId, Object[] arguments)
{
final String defaultResource
= "rhino_ast.java.com.google.javascript.rhino.Messages";
Context cx = Context.getCurrentContext();
Locale locale = cx != null ? cx.getLocale() : Locale.getDefault();
// ResourceBundle does cacheing.
ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale);
String formatString;
try {
formatString = rb.getString(messageId);
} catch (java.util.MissingResourceException mre) {
throw new RuntimeException
("no message resource found for message property "+ messageId);
}
/*
* It's OK to format the string, even if 'arguments' is null;
* we need to format it anyway, to make double ''s collapse to
* single 's.
*/
// TODO: MessageFormat is not available on pJava
MessageFormat formatter = new MessageFormat(formatString);
return formatter.format(arguments);
}
public static EcmaError constructError(String error, String message)
{
int[] linep = new int[1];
String filename = Context.getSourcePositionFromStack(linep);
return
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* CodingConvention defines a set of hooks to customize the behavior of the
* Compiler for a specific team/company.
*
*/
public class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return !isVarArgsParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return parameter.getParent().getLastChild() == parameter;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> exact call flow that
// clients are currently using. In that flow, they call
// getProvides(), then remove the goog.provide calls from the
// AST, and then call getProvides() again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's ok to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info = (new JsFileParser(errorManager)).parseFile(
getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.getType() == Token.CALL) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
parent.getType() != Token.EXPR_RESULT &&
parent.getType() != Token.SCRIPT) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
case Token.FINALLY:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return processEmptyExpression((EmptyExpression) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*/
public final class NodeUtil {
final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty";
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getExpressionBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getExpressionBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getExpressionBooleanValue(n.getLastChild());
return value.not();
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> case Token.AND: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getExpressionBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getExpressionBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
default:
return getBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
*/
static TernaryValue getBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NULL:
case Token.FALSE:
case Token.VOID:
return TernaryValue.FALSE;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
return TernaryValue.TRUE;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): Convert constant array, object, and regex literals as well.
switch (n.getType()) {
case Token.STRING:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
double value = n.getDouble();
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>(n.getDouble());
}
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
}
return null;
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
return Double.NaN;
case Token.NAME:
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
}
return null;
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getNearestFunctionName(Node n) {
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.STRING:
// Return the name of the literal's key.
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured vairables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Uniary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
public static Node newExpr(Node child) {
Node expr = new Node(Token.EXPR_RESULT, child)
.copyInformationFrom(child);
return expr;
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
//
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType())) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (isName(assignTarget)) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (callNode.getType() != Token.NEW) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.getType() == Token.NAME &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(
Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (callNode.getType() != Token.CALL) {
throw new IllegalStateException(
"Expected CALL node, got " + Token
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.getType() == Token.NAME) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.getType() == Token.GETPROP) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Functions in the "Math" namespace have no side effects.
if (nameNode.getFirstChild().getType() == Token.NAME) {
String namespaceName = nameNode.getFirstChild().getString();
if (namespaceName.equals("Math")) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().getType() == Token.REGEXP
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().getType() == Token.STRING
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.getType() == Token.STRING
|| param.getType() == Token.REGEXP))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.ARRAYLIT:
case Token.CALL:
case Token.EMPTY:
case Token.FALSE:
case Token.FUNCTION:
case Token.GETELEM:
case Token.GETPROP:
case Token.GET_REF:
case Token.IF:
case Token.LP:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.RETURN:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op");
}
static boolean isExpressionNode(Node n) {
return n.getType() == Token.EXPR_RESULT;
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
return containsType(n, Token.THIS, new MatchNotFunction());
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR;
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(isName(n));
Node parent = n.getParent();
if (isVar(parent)) {
return n.getFirstChild();
} else if (isAssign(parent) && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this a STRING node?
*/
static boolean isString(Node n) {
return n.getType() == Token.STRING;
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN;
}
/**
* Is this an ASSIGN node?
*/
static boolean isAssign(Node n) {
return n.getType() == Token.ASSIGN;
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.CALL;
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.getType() == Token.FOR
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE,
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (NodeUtil.isFunction(parent)) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
Node parent = n.getParent();
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty();
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
return (n != null && n.getType() == Token.LABEL_NAME);
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.getType() == Token.TRY && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
// Node parent = node.getParent();
if (isStatementBlock(parent)
|| isSwitchCase(node)
|| isTryFinallyNode(parent, node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.getType() == Token.VAR) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (node.getType() == Token.BLOCK) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (parent.getType() == Token.LABEL
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.getType() == Token.FOR
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, new Node(Token.EMPTY));
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return NodeUtil.isCall(node) || NodeUtil.isNew(node);
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.getType() == Token.FUNCTION && !isStatement(n);
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(isFunction(function));
return isNameReferenced(
function.getLastChild(),
"arguments",
new MatchNotFunction());
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.getType() == Token.CALL) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.getType() == Token.STRING) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static Node newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
}
Node function = new Node(Token.FUNCTION, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME, name, lineno, charno));
function.addChildToBack(parameterParen);
function.addChildToBack(body);
return function;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param lineno The source line offset.
* @param charno The source character offset from start of the line.
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name, int lineno, int charno) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name, lineno, charno);
}
Node node = newName(
convention, name.substring(0, endPos), lineno, charno);
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = Node.newString(Token.STRING, part, lineno, charno);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = new Node(Token.GETPROP, node, propNode, lineno, charno);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return A NAME or GETPROP node
*/
static Node newQualifiedNameNode(
CodingConvention convention, String name, Node basisNode,
String originalName) {
Node node = newQualifiedNameNode(convention, name, -1, -1);
setDebugInformation(node, basisNode, originalName);
return node;
}
/**
* Gets the root node of a qualified name. Must be either NAME or THIS.
*/
static Node getRootOfQualifiedName(Node qName) {
for (Node current = qName; true;
current = current.getFirstChild()) {
int type = current.getType();
if (type == Token.NAME || type == Token.THIS) {
return current;
}
Preconditions.checkState(type == Token.GETPROP);
}
}
/**
* Sets the debug information (source file info and orignal name)
* on the given node.
*
* @param node The node on which to set the debug information.
* @param basisNode The basis node from which to copy the source file info.
* @param originalName The original name of the node.
*/
static void setDebugInformation(Node node, Node basisNode,
String originalName) {
node.copyInformationFromForTree(basisNode);
node.putProp(Node.ORIGINALNAME_PROP, originalName);
}
private static Node newName(
CodingConvention convention, String name, int lineno, int charno) {
Node nameNode = Node.newString(Token.NAME, name, lineno, charno);
if (convention.isConstant(name)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
*
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
new MatchNotFunction());
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (isGetProp(cur)) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
// TODO(johnlenz): Why this instead of the more common "undefined"?
Node node = new Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformation
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>From(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
public boolean apply(Node n) {
return n.getType() == Token.NAME
&& n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.getType() == Token.VAR;
}
}
/**
* A predicate for matching anything except function nodes.
*/
static class MatchNotFunction implements Predicate<Node>{
public boolean apply(Node n) {
return !isFunction(n);
}
}
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
public boolean apply(Node n) {
Node parent = n.getParent();
return n.getType() == Token.BLOCK
|| (!isFunction(n) && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name inlucding $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.getType() == Token.GETPROP &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* @param nameNode A name node
* @return The JSDocInfo for the name node
*/
static JSDocInfo getInfoForNameNode(Node nameNode) {
JSDocInfo info = null;
Node parent = null;
if (nameNode != null) {
info = nameNode.getJSDocInfo();
parent = nameNode.getParent();
}
if (info == null && parent != null &&
((parent.getType() == Token.VAR && parent.hasOneChild()) ||
parent.getType() == Token.FUNCTION)) {
info = parent.getJSDocInfo();
}
return info;
}
/**
* Get the JSDocInfo for a function.
*/
static JSDocInfo getFunctionInfo(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
n = n.getParent();
}
return sourceName;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = isName(callTarget);
Node call = new Node(Token.CALL, callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return true;
case Token.FUNCTION:
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(isFunction(function));
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return isString(propNode) && "toString".equals(propNode.getString());
}
return false;
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeUtil.MatchNotFunction;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
/**
* CodeGenerator generates codes from a parse tree, sending it to the specified
* CodeConsumer.
*
*/
class CodeGenerator {
private static final char[] HEX_CHARS
= { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final CodeConsumer cc;
private final CharsetEncoder outputCharsetEncoder;
CodeGenerator(
CodeConsumer consumer, Charset outputCharset) {
cc = consumer;
if (outputCharset == null || outputCharset == Charsets.US_ASCII) {
// If we want our default (pretending to be UTF-8, but escaping anything
// outside of straight ASCII), then don't use the encoder, but
// just special-case the code. This keeps the normal path through
// the code identical to how it's been for years.
this.outputCharsetEncoder = null;
} else {
this.outputCharsetEncoder = outputCharset.newEncoder();
}
}
CodeGenerator(CodeConsumer consumer) {
this(consumer, null);
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary operators
if (opstr != null
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p, context);
cc.addOp(opstr, true);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(last, p, rhsContext);
} else {
addExpr(last, p + 1, rhsContext);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().getType() == Token.BLOCK &&
!first.getNext().hasMoreThanOneChild());
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 2);
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString());
break;
case Token.NAME:
if (first == null
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> || first.getType() == Token.EMPTY) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.getType() == Token.COMMA) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN));
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP));
add("]");
break;
case Token.LP:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
Preconditions.checkState(childCount == 2);
addList(first, false, context);
break;
case Token.NUMBER:
Preconditions.checkState(
childCount ==
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0));
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type));
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1);
cc.addOp(":", true);
addExpr(last, 1);
break;
}
case Token.REGEXP:
if (first.getType() != Token.STRING ||
last.getType() != Token.STRING) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.GET_REF:
add(first);
break;
case Token.REF_SPECIAL:
Preconditions.checkState(childCount == 1);
add(first);
add(".");
add((String) n.getProp(Node.NAME_PROP));
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GET:
case Token.SET:
Preconditions.checkState(n.getParent().getType() == Token.OBJECTLIT);
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.getType() == Token.FUNCTION);
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GET) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
add(jsString(n.getString(), outputCharsetEncoder));
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().getType() == Token.SCRIPT);
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.getType() == Token.VAR) {
cc.endStatement();
}
if (c.getType() == Token.FUNCTION) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> }
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.getType() == Token.VAR) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(
childCount == 2,
"Bad GETPROP: expected 2 children, but got %s", childCount);
Preconditions.checkState(
last.getType() == Token.STRING,
"Bad GETPROP: RHS should be STRING");
boolean needsParens = (first.getType() == Token.NUMBER);
if (needsParens) {
add("(");
}
addLeftExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(
childCount == 2,
"Bad GETELEM: expected 2 children but got %s", childCount);
addLeftExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP);
// A non-
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addLeftExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o, false);
add(first);
}
break;
}
case Token.CALL:
// We have two special cases here:
// 1) If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
// 2) If the left hand side of the call is a property reference,
// then the call must not a FREE_CALL annotation. If it does, then
// that means it was originally an call without an explicit this and
// that must be preserved.
if (isIndirectEval(first)
|| n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) {
add("(0,");
addExpr(first, NodeUtil.precedence(Token.COMMA));
add(")");
} else {
addLeftExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyStatement(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyStatement(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
Preconditions.checkState(childCount == 0);
add(Node.tokenToName(type));
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
case Token.EXPR_RESULT:
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parentheses. Otherwise, when parsed, NEW will bind to the
// first viable parentheses (don't traverse into functions).
if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
if (childCount !=
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)) {
throw new IllegalStateException(
"Unexpected String children: " + n.getParent().toStringTree());
}
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext()) {
if (c != first) {
cc.listSeparator();
}
if (c.getType() == Token.GET || c.getType() == Token.SET) {
add(c);
} else {
// Object literal property names don't have to be quoted if they are
// not JavaScript keywords
if (c.getType() == Token.STRING &&
!TokenStream.isKeyword(c.getString()) &&
TokenStream.isJSIdentifier(c.getString()) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(c.getString())) {
add(c.getString());
} else {
addExpr(c, 1);
}
add(":");
addExpr(c.getFirstChild(), 1);
}
}
add("}");
if (needsParens)
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.endBlock(context == Context.STATEMENT);
break;
case Token.CASE:
Preconditions.checkState(childCount == 2);
add("case ");
add(first);
addCaseBody(last);
break;
case Token.DEFAULT:
Preconditions.checkState(childCount == 1);
add("default");
addCaseBody(first);
break;
case Token.LABEL:
Preconditions.checkState(childCount == 2);
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(first);
add(":");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), true);
break;
// This node is auto generated in anonymous functions and should just get
// ignored for our purposes.
case Token.SETNAME:
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.getType() == Token.NAME && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyStatement(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && n.getType() != Token.BLOCK) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.getType() == Token.BLOCK) {
int count = getNonEmptyChildCount(n, 2);
if (count == 0) {
if (cc.shouldPreserveExtraBlocks()) {
cc.beginBlock();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
} else {
cc.endStatement(true);
}
return;
}
if (count == 1) {
// Hack around a couple of browser bugs:
// Safari needs a block around function declarations.
// IE6/7 needs a block around DOs.
Node firstAndOnlyChild = getFirstNonEmptyChild(n);
boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks();
if (alwaysWrapInBlock || isOne
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>ExactlyFunctionOrDo(firstAndOnlyChild)) {
cc.beginBlock();
add(firstAndOnlyChild, Context.STATEMENT);
cc.maybeLineBreak();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
return;
} else {
// Continue with the only child.
nodeToProcess = firstAndOnlyChild;
}
}
if (count > 1) {
context = Context.PRESERVE_BLOCK;
}
}
if (nodeToProcess.getType() == Token.EMPTY) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.getType() == Token.VAR) {
cc.endStatement();
}
}
}
/**
* @return Whether the Node is a DO or FUNCTION (with or without
* labels).
*/
private boolean isOneExactlyFunctionOrDo(Node n) {
if (n.getType() == Token.LABEL) {
Node labeledStatement = n.getLastChild();
if (labeledStatement.getType() != Token.BLOCK) {
return isOneExactlyFunctionOrDo(labeledStatement);
} else {
// For labels with block children, we need to ensure that a
// labeled FUNCTION or DO isn't generated when extraneous BLOCKs
// are skipped.
if (getNonEmptyChildCount(n, 2) == 1) {
return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n));
} else {
// Either a empty statement or an block with more than one child,
// way it isn't a FUNCTION or DO.
return false;
}
}
} else {
return (n.getType() == Token.FUNCTION || n.getType() == Token.DO);
}
}
/**
* Adds a node at the left-hand side of an expression. Unlike
* {@link #addExpr(Node,int)}, this preserves information about the context.
*
* The left side of an expression is special because in the JavaScript
* grammar, certain tokens may be parsed differently when they are at
* the beginning of a statement. For example, "{}" is parsed as a block,
* but "{'x': 'y'}" is parsed as an object literal.
*/
void addLeftExpr(Node n, int minPrecedence, Context context) {
addExpr(n, minPrecedence, context);
}
void addExpr(Node n, int minPrecedence) {
addExpr(n, minPrecedence, Context.OTHER);
}
private void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.getType() == Token.IN))){
add("(");
add(n
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
if (info == null) {
return this;
} else {
return inferParameterTypes(info);
}
}
// arguments
Node oldParameterType = null;
if (parametersNode != null) {
oldParameterType = parametersNode.getFirstChild();
}
FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
boolean warnedAboutArgList = false;
Set<String> allJsDocParams = (info == null) ?
Sets.<String>newHashSet() :
Sets.newHashSet(info.getParameterNames());
boolean foundTemplateType = false;
for (Node arg : argsParent.children()) {
String argumentName = arg.getString();
allJsDocParams.remove(argumentName);
// type from JSDocInfo
JSType parameterType = null;
boolean isOptionalParam = isOptionalParameter(arg, info);
boolean isVarArgs = isVarArgsParameter(arg, info);
if (info != null && info.hasParameterType(argumentName)) {
parameterType =
info.getParameterType(argumentName).evaluate(scope, typeRegistry);
} else if (oldParameterType != null &&
oldParameterType.getJSType() != null) {
parameterType = oldParameterType.getJSType();
isOptionalParam = oldParameterType.isOptionalArg();
isVarArgs = oldParameterType.isVarArgs();
} else {
parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (templateTypeName != null &&
parameterType.restrictByNotNullOrUndefined().isTemplateType()) {
if (foundTemplateType) {
reportError(TEMPLATE_TYPE_DUPLICATED, fnName);
}
foundTemplateType = true;
}
warnedAboutArgList |= addParameter(
builder, parameterType, warnedAboutArgList,
isOptionalParam,
isVarArgs);
if (oldParameterType != null) {
oldParameterType = oldParameterType.getNext();
}
}
if (templateTypeName != null && !foundTemplateType) {
reportError(TEMPLATE_TYPE_EXPECTED, fnName);
}
for (String inexistentName : allJsDocParams) {
reportWarning(INEXISTANT_PARAM, inexistentName, fnName);
}
parametersNode = builder.build();
return this;
}
/**
* @return Whether the given param is an optional param.
*/
private boolean isOptionalParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isOptionalParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isOptionalArg();
}
/**
* Determine whether this is a var args parameter.
* @return Whether the given param is a var args param.
*/
private boolean isVarArgsParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isVarArgsParameter(param)) {
return true;
}
String paramName = param.getString
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.declaration;
if (!isDefined) {
if (declaration != null) {
reportRefToUndefinedName(name, declaration);
}
}
if (name.refs != null) {
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.refs) {
if (!isDefined) {
reportRefToUndefinedName(name, ref);
} else {
if (declaration != null &&
ref.module != declaration.module &&
!moduleGraph.dependsOn(ref.module, declaration.module)) {
reportBadModuleReference(name, ref);
}
}
}
}
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME,
ref.module.getName(), name.declaration.module.getName(),
name.fullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return;
}
}
compiler.report(
JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING,
name.fullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private static boolean propertyMustBeInitializedByFullName(Name name) {
// If an object literal in the global namespace is never aliased,
// then all of its
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* This describes the Closure-specific JavaScript coding conventions.
*
*/
public class ClosureCodingConvention extends DefaultCodingConvention {
private static final long serialVersionUID = 1L;
private static final String TYPEDEF_NAME = "goog.typedef";
static final DiagnosticType OBJECTLIT_EXPECTED = DiagnosticType.warning(
"JSC_REFLECT_OBJECTLIT_EXPECTED",
"Object literal expected as second argument");
/**
* Closure's goog.inherits adds a {@code superClass_} property to the
* subclass, and a {@code constructor} property.
*/
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
if (type == SubclassType.INHERITS) {
childCtor.defineDeclaredProperty("superClass_",
parentCtor.getPrototype(), false);
childCtor.getPrototype().defineDeclaredProperty("constructor",
childCtor, false);
}
}
/**
* {@inheritDoc}
*
* <p>Understands several different inheritance patterns that occur in
* Google code (various uses of {@code inherits} and {@code mixin}).
*/
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
Node callName = callNode.getFirstChild();
SubclassType type = typeofClassDefiningName(callName);
if (type != null) {
Node subclass = null;
Node superclass = callNode.getLastChild();
// There are six possible syntaxes for a class-defining method:
// SubClass.inherits(SuperClass)
// goog.inherits(SubClass, SuperClass)
// goog$inherits(SubClass, SuperClass
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>)
// SubClass.mixin(SuperClass.prototype)
// goog.mixin(SubClass.prototype, SuperClass.prototype)
// goog$mixin(SubClass.prototype, SuperClass.prototype)
if (callNode.getChildCount() == 2 &&
callName.getType() == Token.GETPROP) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
// make sure to strip the prototype off of the nodes
// to normalize for goog.mixin
return new SubclassRelationship(
type,
stripPrototype(subclass),
stripPrototype(superclass));
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.getType() == Token.GETPROP) {
methodName = callName.getLastChild().getString();
} else if (callName.getType() == Token.NAME) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName);
}
/**
* Given a qualified name node, strip "prototype" off the end.
*
* Examples of this transformation:
* a.b.c => a.b.c
* a.b.c.prototype => a.b.c
*/
private Node stripPrototype(Node qualifiedName) {
if (qualifiedName.getType() == Token.GETPROP &&
qualifiedName.getLastChild().getString().equals("prototype")) {
return qualifiedName.getFirstChild();
}
return qualifiedName;
}
/**
* Exctracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Exctracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.getType() == Token.GETPROP) {
String qualifiedName = callee.getQualifiedName();
if ((functionName).equals(qualifiedName)) {
className = callee.getNext().getString();
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.getType() == Token.ARRAYLIT) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.getType() == Token.STRING) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return null;
}
@Override
public String identifyTypeDefAssign(Node n) {
Node firstChild = n.getFirstChild();
int type = n.getType();
if (type == Token.ASSIGN) {
if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) {
return firstChild.getQualifiedName();
}
} else if (type == Token.VAR && firstChild.hasChildren()) {
if (TYPEDEF_NAME.equals(
firstChild.getFirstChild().getQualifiedName())) {
return firstChild.getString();
}
}
return null;
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.add
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>SingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return null;
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false);
functionType.defineDeclaredProperty("instance_", objectType, false);
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.getType() == Token.CALL);
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName());
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
Preconditions.checkArgument(callNode.getType() == Token.CALL);
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callName.getChildCount() != 2) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (objectNode.getType() != Token.OBJECTLIT) {
t.getCompiler().report(JSError.make(t.getSourceName(), callNode,
OBJECTLIT_EXPECTED));
return null;
}
return new ObjectLiteralCast(typeNode.getQualifiedName(),
typeNode.getNext());
}
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return ImmutableList.<AssertionFunctionSpec>of(
new AssertionFunctionSpec("goog.asserts.assert"),
new AssertionFunctionSpec("goog.asserts.assertNumber",
JSTypeNative.NUMBER_TYPE),
new AssertionFunctionSpec("goog.asserts.assertString",
JSTypeNative.STRING_TYPE),
new AssertionFunctionSpec("goog.asserts.assertFunction",
JSTypeNative.FUNCTION_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray",
JSTypeNative.ARRAY_TYPE),
// TODO(agrieve): It would be better if this could make the first
// parameter the type
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Node) node).getDouble());
}
private double number;
}
private static class StringNode extends Node {
private static final long serialVersionUID = 1L;
StringNode(int type, String str) {
super(type);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
StringNode(int type, String str, int lineno, int charno) {
super(type, lineno, charno);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
/**
* returns the string content.
* @return non null.
*/
@Override
public String getString() {
return this.str;
}
/**
* sets the string content.
* @param str the new value. Non null.
*/
@Override
public void setString(String str) {
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
@Override
public boolean isEquivalentTo(Node node) {
return (node instanceof StringNode &&
this.str.equals(((StringNode) node).str));
}
/**
* If the property is not defined, this was not a quoted key. The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems are immutable so that they can be shared.
private static class PropListItem implements Serializable {
private static final long serialVersionUID = 1L;
final PropListItem next;
final int type;
final int intValue;
final Object objectValue;
PropListItem(int type, int intValue, PropListItem next) {
this(type, intValue, null, next);
}
PropListItem(int type, Object objectValue, PropListItem next) {
this(type, 0, objectValue, next);
}
PropListItem(
int type, int intValue, Object objectValue, PropListItem next) {
this.type = type;
this.intValue = intValue;
this.objectValue = objectValue;
this.next = next;
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> public int getLineno() {
return extractLineno(sourcePosition);
}
public int getCharno() {
return extractCharno(sourcePosition);
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
return String.valueOf(type);
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first.getType() == Token.STRING) {
sb.append(first.getString());
}
} else if (this instanceof ScriptOrFnNode) {
ScriptOrFnNode sof = (ScriptOrFnNode) this;
if (this instanceof FunctionNode) {
FunctionNode fn = (FunctionNode) this;
sb.append(' ');
sb.append(fn.getFunctionName());
}
if (printSource) {
sb.append(" [source name: ");
sb.append(
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.getChildCount()
&& getClass() == node2.getClass()
&& JSType.isEquivalent(jsType, node2.getJSType())) {
eq = this.isEquivalentTo(node2);
}
if (!eq) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.EOF: return "eof";
case Token.EOL: return "eol";
case Token.ENTERWITH: return "enterwith";
case Token.LEAVEWITH: return "leavewith";
case Token.RETURN: return "return";
case Token.GOTO: return "goto";
case Token.IFEQ: return "ifeq";
case Token.IFNE: return "ifne";
case Token.SETNAME: return "setname";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bitxor";
case Token.BITAND: return "bitand";
case Token.EQ: return "eq";
case Token.NE: return "ne";
case Token.LT: return "lt";
case Token.LE: return "le";
case Token.GT: return "gt";
case Token.GE: return "ge";
case Token.LSH: return "lsh";
case Token.RSH: return "rsh";
case Token.URSH: return "ursh";
case Token.ADD: return "add";
case Token.SUB: return "sub";
case Token.MUL: return "mul";
case Token.DIV: return "div";
case Token.MOD: return "mod";
case Token.BITNOT: return "bitnot";
case Token.NEG: return "neg";
case Token.NEW: return "new";
case Token.DELPROP: return "delprop";
case Token.TYPEOF: return "typeof";
case Token.GETPROP: return "getprop";
case Token.SETPROP: return "setprop";
case Token.GETELEM: return "getelem";
case Token.SETELEM: return "setelem";
case Token.CALL: return "call";
case Token.NAME: return "name";
case Token.NUMBER: return "number";
case Token.STRING: return "string";
case Token.NULL: return "null";
case Token.THIS: return "this";
case Token
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>continue";
case Token.VAR: return "var";
case Token.WITH: return "with";
case Token.CATCH: return "catch";
case Token.FINALLY: return "finally";
case Token.RESERVED: return "reserved";
case Token.NOT: return "not";
case Token.VOID: return "void";
case Token.BLOCK: return "block";
case Token.ARRAYLIT: return "arraylit";
case Token.OBJECTLIT: return "objectlit";
case Token.LABEL: return "label";
case Token.TARGET: return "target";
case Token.LOOP: return "loop";
case Token.EXPR_VOID: return "expr_void";
case Token.EXPR_RESULT: return "expr_result";
case Token.JSR: return "jsr";
case Token.SCRIPT: return "script";
case Token.EMPTY: return "empty";
case Token.GET_REF: return "get_ref";
case Token.REF_SPECIAL: return "ref_special";
}
return "<unknown="+token+">";
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
if (type == Token.ARRAYLIT) {
try {
int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP);
int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
if (indices1 == null) {
if (indices2 != null) {
return false;
}
} else if (indices2 == null) {
return false;
} else if (indices1.length != indices2.length) {
return false;
} else {
for (int i = 0; i < indices1.length; i++) {
if (indices1[i] != indices2[i]) {
return false;
}
}
}
} catch (Exception e) {
return false;
}
} else if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
return true;
}
public boolean hasSideEffects() {
switch (type) {
case Token.EXPR_VOID:
case Token.COMMA:
if (last != null)
return last.hasSideEffects();
else
return true;
case Token.HOOK:
if (first == null || first.next == null || first.next.next == null) {
Kit.code
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>Bug();
}
return first.next.hasSideEffects() && first.next.next.hasSideEffects();
case Token.ERROR: // Avoid cascaded error messages
case Token.EXPR_RESULT:
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
case Token.IF:
case Token.ELSE:
case Token.SWITCH:
case Token.WHILE:
case Token.DO:
case Token.FOR:
case Token.BREAK:
case Token.CONTINUE:
case Token.VAR:
case Token.CONST:
case Token.WITH:
case Token.CATCH:
case Token.FINALLY:
case Token.BLOCK:
case Token.LABEL:
case Token.TARGET:
case Token.LOOP:
case Token.JSR:
case Token.SETPROP_OP:
case Token.SETELEM_OP:
case Token.LOCAL_BLOCK:
case Token.SET_REF_OP:
return true;
default:
return false;
}
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
return getString();
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS><String>) getProp(DIRECTIVES);
}
/**
* Adds a warning to be suppressed. This is indistinguishable
* from having a {@code @suppress} tag in the code.
*/
public void addSuppression(String warning) {
if (getJSDocInfo() == null) {
setJSDocInfo(new JSDocInfo(false));
}
getJSDocInfo().addSuppression(warning);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setWasEmptyNode(boolean val) {
putBooleanProp(EMPTY_BLOCK, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean wasEmptyNode() {
return getBooleanProp(EMPTY_BLOCK);
}
// There are four values of interest:
// global state changes
// this state changes
// arguments state changes
// whether the call throws an exception
// locality of the result
// We want a value of 0 to mean "global state changes and
// unknown locality of result".
final public static int FLAG_GLOBAL_STATE_UNMODIFIED = 1;
final public static int FLAG_THIS_UNMODIFIED = 2;
final public static int FLAG_ARGUMENTS_UNMODIFIED = 4;
final public static int FLAG_NO_THROWS = 8;
final public static int FLAG_LOCAL_RESULTS = 16;
final public static int SIDE_EFFECTS_FLAGS_MASK = 31;
final public static int SIDE_EFFECTS_ALL = 0;
final public static int NO_SIDE_EFFECTS =
FLAG_GLOBAL_STATE_UNMODIFIED
| FLAG_THIS_UNMODIFIED
| FLAG_ARGUMENTS_UNMODIFIED
| FLAG_NO_THROWS;
/**
* Marks this function or constructor call's side effect flags.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope, false));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (forgiving) {
namedType.forgiveUnknownNames();
}
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope, false);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope, false);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope, false)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), "", contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.LP) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope, false));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope, false);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), "", arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope, false);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope, false);
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
builder.addProperty(fieldName, fieldType);
}
return builder.build();
}
/**
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
return n == parent.getLastChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.
*/
public Scope createScope(Node root, Scope parent) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type instanceof FunctionType) {
FunctionType fnType = (FunctionType) type;
ObjectType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes);
}
return newScope;
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry));
Scope s = new Scope(root, compiler);
declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE);
declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, DATE_FUNCTION_TYPE);
declareNativeFunctionType(s, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
declareNativeValueType(s, "undefined", VOID_TYPE);
// The typedef construct needs the any type, so that it can be assigned
// to anything. This is kind of a hack, and an artifact of the typedef
// syntax we've chosen.
declareNativeValueType(s, LEGACY_TYPEDEF, NO_TYPE);
// ActiveXObject is unqiuely special, because it can be used to construct
// any type (the type that it creates is related to the arguments you
// pass to it).
declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
Node nameNode = null;
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getInfoForNameNode(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.getType() == Token.ASSIGN) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
if (valueNode != null &&
LEGACY_TYPEDEF.equals(valueNode.getQualifiedName())) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
/**
* Given a node, determines whether that node names a prototype
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> * property, and if so, returns the qualified name node representing
* the owner of that property. Otherwise, returns null.
*/
private static Node getPrototypePropertyOwner(Node n) {
if (n.getType() == Token.GETPROP) {
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.getLastChild().getString().equals("prototype")) {
Node maybeOwner = firstChild.getFirstChild();
if (maybeOwner.isQualifiedName()) {
return maybeOwner;
}
}
}
return null;
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're builidng.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (n.getType() == Token.FUNCTION ||
n.getType() == Token.SCRIPT) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
attachLiteralTypes(n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// VARs and ASSIGNs are handled in different branches of this
// switch statement.
if (parent.getType() != Token.ASSIGN &&
parent.getType() != Token.NAME) {
defineDeclaredFunction(n, parent);
}
break;
case Token.ASSIGN:
// Handle constructor and enum definitions.
defineNamedTypeAssign(n, parent);
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.getType() == Token.EXPR_RESULT &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
}
private void attachLiteralTypes(Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.REF_SPECIAL:
n.setJSType(getNativeType(UNKNOWN_TYPE));
break;
case Token.OBJECTLIT:
processObjectLit(n);
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void processObjectLit(Node objectLit) {
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
JSType type = lendsVar.getType();
if (type == null) {
type = typeRegistry
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>.getNativeType(UNKNOWN_TYPE);
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
} else {
objectLit.setJSType(type);
}
}
}
if (objectLit.getJSType() == null) {
objectLit.setJSType(typeRegistry.createAnonymousObjectType());
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
JSType getDeclaredTypeInAnnotation(
NodeTraversal t, Node node, JSDocInfo info) {
return getDeclaredTypeInAnnotation(t.getSourceName(), node, info);
}
JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode = node.getType() == Token.GETPROP ?
node.getFirstChild() : null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
// constructors are often handled separately.
if (info.isConstructor() && typeRegistry.getType(fnName) != null) {
return null;
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(
fnName, compiler, node, sourceName, scope)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferParameterTypes(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (objNode != null) {
if (objNode.getType() == Token.GETPROP &&
objNode.getLastChild().getString().equals("prototype")) {
builder.inferThisType(info, objNode.getFirstChild());
searchedForThisType = true;
} else if (objNode.getType() == Token.THIS) {
builder.inferThisType(info, objNode.getJSType());
searchedForThisType = true;
}
}
if (!searchedForThisType) {
builder.inferThisType(info, (Node) null);
}
jsType = builder.buildAndRegister();
}
}
return jsType;
}
/**
* Asserts that it's ok to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a declared function.
*/
void defineDeclaredFunction(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
JSDocInfo info = n.getJSDocInfo();
int parentType = parent.getType();
Preconditions.checkState(
(scope.isLocal() || parentType != Token.ASSIGN) &&
parentType != Token.NAME,
"function defined as standalone function when it is being " +
"assigned");
String functionName = n.getFirstChild().getString();
FunctionType functionType = getFunctionType(functionName, n, info,
null);
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a qualified name assign to an enum or constructor.
*/
void defineNamedTypeAssign(Node n, Node parent) {
assertDefinitionNode(n, Token.ASSIGN);
JSDocInfo info = n.getJSDocInfo();
// TODO(nicksantos): We should support direct assignment to a
// prototype, as in:
// Foo.prototype = {
// a: function() { ... },
// b: function() { ... }
// };
// Right now (6/23/08), we understand most of this syntax, but we
// don't tie the "a" and "b" methods to the context of Foo.
Node rvalue = n.getLastChild();
Node lvalue = n.getFirstChild();
info = (info != null) ? info : rvalue.getJSDocInfo();
if (rvalue.getType() == Token.FUNCTION ||
info != null && info.isConstructor()) {
getFunctionType(lvalue.getQualifiedName(), rvalue, info,
lvalue);
} else if (info != null && info.hasEnumParameterType()) {
JSType type = getEnumType(lvalue.getQualifiedName(), n, rvalue,
info.getEnumParameterType().evaluate(scope, typeRegistry));
if (type != null) {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
setDeferredType(lvalue, type);
}
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value = name.getFirstChild();
if (value != null && value.getType() == Token.FUNCTION) {
// function
String functionName = name.getString();
FunctionType functionType =
getFunctionType(functionName, value, info, null);
if (functionType.isReturnTypeInferred() &&
scope.isLocal()) {
defineSlot(name, var, null);
} else {
defineSlot(name, var, functionType);
}
} else {
// variable's type
JSType type = null;
if (info == null) {
// the variable's type will be inferred
CompilerInput input = compiler.getInput(sourceName);
Preconditions.checkNotNull(input, sourceName);
type = input.isExtern() ?
getNativeType(UNKNOWN_TYPE) : null;
} else if (info.hasEnumParameterType()) {
type = getEnumType(name.getString(), var, value,
info.getEnumParameterType().evaluate(scope, typeRegistry));
} else if (info.isConstructor()) {
type = getFunctionType(name.getString(), value, info, name);
} else {
type = getDeclaredTypeInAnnotation(sourceName, name, info);
}
defineSlot(name, var, type);
}
}
/**
* Gets the function type from the function node and its attached
* {@link JSDocInfo}.
* @param name the function's name
* @param rValue the function node. It must be a {@link Token#FUNCTION}.
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType getFunctionType(String name,
Node rValue, JSDocInfo info, @Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global function aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof FunctionType) {
functionType = (FunctionType)
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> var.getType();
if (functionType != null &&
(functionType.isConstructor() || functionType.isInterface())) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
return functionType;
}
Node owner = null;
if (lvalueNode != null) {
owner = getPrototypePropertyOwner(lvalueNode);
}
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.getType() == Token.FUNCTION;
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (functionType == null && info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = (FunctionType) type;
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
FunctionType overriddenPropType = null;
if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP &&
lvalueNode.isQualifiedName()) {
Var var = scope.getVar(
lvalueNode.getFirstChild().getQualifiedName());
if (var != null) {
ObjectType ownerType = ObjectType.cast(var.getType());
if (ownerType != null) {
String propName = lvalueNode.getLastChild().getString();
overriddenPropType = findOverriddenFunction(ownerType, propName);
}
}
}
functionType =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setSourceNode(fnRoot)
.inferFromOverriddenFunction(overriddenPropType, parametersNode)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferInheritance(info)
.inferThisType(info, owner)
.inferParameterTypes(parametersNode, info)
.inferReturnStatementsAsLastResort(fnBlock)
.buildAndRegister();
}
// assigning the function type to the function node
if (rValue != null) {
setDeferredType(rValue, functionType);
}
// all done
return functionType;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private FunctionType findOverriddenFunction(
ObjectType ownerType, String propName) {
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType instanceof FunctionType) {
return (FunctionType) propType;
} else {
// If it's not, then check to see if it's implemented
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualfied names.
boolean shouldDeclareOnGlobalThis = false;
if (n.getType() == Token.NAME) {
Preconditions.checkArgument(
parent.getType() == Token.FUNCTION ||
parent.getType() == Token.VAR ||
parent.getType() == Token.LP ||
parent.getType() == Token.CATCH);
shouldDeclareOnGlobalThis = scope.isGlobal() &&
(parent.getType() == Token.VAR ||
parent.getType() == Token.FUNCTION);
} else {
Preconditions.checkArgument(
n.getType() == Token.GETPROP &&
(parent.getType() == Token.ASSIGN ||
parent.getType() == Token.EXPR_RESULT));
}
String variableName = n.getQualifiedName();
Preconditions.checkArgument(!variableName.isEmpty());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.getType() == Token.GETPROP && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// declared in closest scope?
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
validator.expectUndeclaredVariable(
sourceName, n, parent, oldVar, variableName, type);
} else {
if (!inferred) {
setDeferredType(n, type);
}
CompilerInput input = compiler.getInput(sourceName);
boolean isExtern = input.isExtern();
Var newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS);
if (
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
isExtern);
} else {
globalThis.defineDeclaredProperty(variableName, type, isExtern);
}
}
// We need to do some additional work for constructors and interfaces.
if (type instanceof FunctionType &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
FunctionType fnType = (FunctionType) type;
if ((fnType.isConstructor() || fnType.isInterface()) &&
!fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
scopeToDeclareIn.declare(variableName + ".prototype", n,
fnType.getPrototype(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!isExtern &&
// We want to make sure that when we declare a new instance
// type (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors
// (like function(new:Array). Checking the constructed
// type against the variable name is a sufficient check for
// this.
variableName.equals(
fnType.getInstanceType().getReferenceName())) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.getType() == Token.NAME) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.isGlobal();
}
}
return false;
}
/**
* Look for a type declaration on a GETPROP node.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info,
Node n, @Nullable Node rhsValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(t, n, info);
} else if (info != null && info
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor = delegateBaseObject.getConstructor();
FunctionType delegateSuperCtor = delegateSuperObject.getConstructor();
if (delegatorCtor != null && delegateBaseCtor != null &&
delegateSuperCtor != null) {
FunctionParamBuilder functionParamBuilder =
new FunctionParamBuilder(typeRegistry);
functionParamBuilder.addRequiredParams(
getNativeType(U2U_CONSTRUCTOR_TYPE));
FunctionType findDelegate = typeRegistry.createFunctionType(
typeRegistry.createDefaultObjectUnion(delegateBaseObject),
functionParamBuilder.build());
FunctionType delegateProxy = typeRegistry.createConstructorType(
delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX,
null, null, null);
delegateProxy.setPrototypeBasedOn(delegateBaseObject);
codingConvention.applyDelegateRelationship(
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
if (!qVar.isTypeInferred()) {
// Just ignore assigns to declared prototypes.
return;
}
if (qVar.getScope() == scope) {
scope.undeclare(qVar);
}
}
}
// Precedence of type information on GETPROPs:
// 1) @type annnotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4) ASSIGN to anything else
//
// 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff
// the function has not
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3
JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #4
valueType = rhsValue.getJSType();
}
if (valueType == null) {
if (parent.getType() == Token.EXPR_RESULT) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3
inferred = !(info.hasType() || info.hasEnumParameterType() ||
FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred) {
// Determining declaration for #2
inferred = !(rhsValue != null &&
rhsValue.getType() == Token.FUNCTION &&
!scope.isDeclared(qName, false));
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, isExtern);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null &&
rhsValue.getType() == Token.TRUE) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType instanceof FunctionType) {
JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE),
true);
}
}
}
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS>
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub delcarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, isExtern);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final ObjectType thisType;
CollectProperties(ObjectType thisType) {
this.thisType = thisType;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.EXPR_RESULT) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
member.getType() != Token.GETPROP ||
member.getFirstChild().getType() != Token.THIS) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> = getDeclaredGetPropType(t, info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.getType() == Token.NAME || name.getType() == Token.STRING)) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
false /* functions with implementations are not in externs */);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.ASSIGN:
// Handle typedefs.
checkForOldStyleTypedef(t, n);
break;
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForOldStyleTypedef(t, n);
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.getType() == Token.GETPROP) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate An ASSIGN or VAR node.
*/
// TODO(nicksantos): Kill this.
private void checkForOldStyleTypedef(NodeTraversal t, Node candidate) {
// old-style typedefs
String typedef = codingConvention.identifyTypeDefAssign(candidate);
if (typedef != null) {
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSDocInfo info = candidate.getJSDocInfo();
JSType realType = null;
if (info != null && info.getType() != null) {
realType = info.getType().evaluate(scope, typeRegistry);
}
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
// Duplicate typedefs get handled when we try to register
// this typedef in the scope.
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're builidng.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.getType() == Token.LP && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.
Closure, 87
<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB>
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(lastTrue, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isFoldableExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node maybeExpr = n.getFirstChild();
<CHANGES>
<CHANGEE>
// IE has a bug where event handlers behave differently when
// their return value is used vs. when their return value is in
// an EXPR_RESULT. It's pretty freaking weird. See:
// http://code.google.com/p/closure-compiler/issues/detail?id=291
// We try to detect this case, and not fold EXPR_RESULTs
// into other expressions.
<CHANGES>
<CHANGEE>
// We only have to worry about methods with an implicit 'this'
// param, or this doesn't happen.
<CHANGES>
return NodeUtil.isExpressionNode(maybeExpr);
<CHANGEE>
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isFoldableExpressBlock(n));
return n.getFirstChild();
}
/**
<FILEE>
<SCANS> an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source) {
return new FunctionType(registry, name, source);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely.
return isEquivalentTo(registry.getNativeType(U2U_CONSTRUCTOR_TYPE));
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
@Override
public boolean isFunctionType() {
return true;
}
@Override
public boolean canBeCalled() {
return true;
}
public Iterable<Node> getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments that this function requires,
* or Integer.MAX_VALUE if this is a variable argument function.
*/
public int getMaxArguments() {
Node params = getParametersNode();
if (params != null) {
Node lastParam = params.getLastChild();
if (lastParam == null || !lastParam.isVarArgs()) {
return params.getChildCount();
}
}
return Integer.MAX_VALUE;
}
public JSType getReturnType() {
return call.returnType;
}
public boolean isReturnTypeInferred() {
return call.returnTypeInferred;
}
/** Gets the internal arrow type. For use by subclasses only. */
ArrowType getInternalArrowType() {
return call;
}
/**
* Gets the {@code prototype} property of this function type. This is
* equivalent to {@code (ObjectType) getPropertyType("prototype")}.
*/
public FunctionPrototypeType getPrototype() {
// lazy initialization of the prototype field
if (prototype == null) {
setPrototype(new FunctionPrototypeType(registry, this, null));
}
return prototype;
}
/**
* Sets the prototype, creating the prototype object from the given
* base type.
* @param baseType The base type.
*/
public void setPrototypeBasedOn(ObjectType base